From b5db5975e561f5594245a21400eafd9e8d92f00b Mon Sep 17 00:00:00 2001 From: Dmitriy Shekhovtsov Date: Fri, 16 Sep 2016 17:15:38 +0300 Subject: [PATCH] fix(modal): fixing hack which gets root viewContainerRef to attach backdrop fixes #975, fixes #854 --- components/modal/modal.component.ts | 77 +++++++++---------- components/utils/components-helper.service.ts | 22 ++---- 2 files changed, 44 insertions(+), 55 deletions(-) diff --git a/components/modal/modal.component.ts b/components/modal/modal.component.ts index fc82225424..aa208a6fe1 100644 --- a/components/modal/modal.component.ts +++ b/components/modal/modal.component.ts @@ -9,7 +9,6 @@ import { ElementRef, EventEmitter, HostListener, - Injector, Input, OnDestroy, Output, @@ -32,41 +31,41 @@ const BACKDROP_TRANSITION_DURATION = 150; }) export class ModalDirective implements AfterViewInit, OnDestroy { @Input() - public set config(conf:ModalOptions) { + public set config(conf: ModalOptions) { this._config = this.getConfig(conf); }; - @Output() public onShow:EventEmitter = new EventEmitter(); - @Output() public onShown:EventEmitter = new EventEmitter(); - @Output() public onHide:EventEmitter = new EventEmitter(); - @Output() public onHidden:EventEmitter = new EventEmitter(); + @Output() public onShow: EventEmitter = new EventEmitter(); + @Output() public onShown: EventEmitter = new EventEmitter(); + @Output() public onHide: EventEmitter = new EventEmitter(); + @Output() public onHidden: EventEmitter = new EventEmitter(); - public get config():ModalOptions { + public get config(): ModalOptions { return this._config; } // seems like an Options - public isAnimated:boolean = true; + public isAnimated: boolean = true; - public get isShown():boolean { + public get isShown(): boolean { return this._isShown; } // todo: implement _dialog - protected _dialog:any; + protected _dialog: any; - protected _config:ModalOptions; - protected _isShown:boolean = false; + protected _config: ModalOptions; + protected _isShown: boolean = false; - private isBodyOverflowing:boolean = false; - private originalBodyPadding:number = 0; - private scrollbarWidth:number = 0; + private isBodyOverflowing: boolean = false; + private originalBodyPadding: number = 0; + private scrollbarWidth: number = 0; // reference to backdrop component - private backdrop:ComponentRef; + private backdrop: ComponentRef; - private get document():any { + private get document(): any { return this.componentsHelper.getDocument(); }; @@ -74,7 +73,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy { // @HostBinding(`class.${ClassName.IN}`) private _addClassIn:boolean; @HostListener('click', ['$event']) - protected onClick(event:any):void { + protected onClick(event: any): void { if (this.config.ignoreBackdropClick || this.config.backdrop === 'static' || event.target !== this.element.nativeElement) { return; } @@ -84,19 +83,18 @@ export class ModalDirective implements AfterViewInit, OnDestroy { // todo: consider preventing default and stopping propagation @HostListener('keydown.esc') - protected onEsc():void { + protected onEsc(): void { if (this.config.keyboard) { this.hide(); } } - public constructor(private element:ElementRef, - private renderer:Renderer, - private injector:Injector, - private componentsHelper:ComponentsHelper) { + public constructor(private element: ElementRef, + private renderer: Renderer, + private componentsHelper: ComponentsHelper) { } - public ngOnDestroy():any { + public ngOnDestroy(): any { this.config = void 0; // this._element = null // this._dialog = null @@ -107,17 +105,17 @@ export class ModalDirective implements AfterViewInit, OnDestroy { this.scrollbarWidth = void 0; } - public ngAfterViewInit():any { + public ngAfterViewInit(): any { this._config = this._config || this.getConfig(); } /** Public methods */ - public toggle(/*relatedTarget?:ViewContainerRef*/):void { + public toggle(/*relatedTarget?:ViewContainerRef*/): void { return this._isShown ? this.hide() : this.show(/*relatedTarget*/); } - public show(/*relatedTarget?:ViewContainerRef*/):void { + public show(/*relatedTarget?:ViewContainerRef*/): void { this.onShow.emit(this); if (this._isShown) { return; @@ -137,7 +135,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy { }); } - public hide(event?:Event):void { + public hide(event?: Event): void { if (event) { event.preventDefault(); } @@ -161,14 +159,14 @@ export class ModalDirective implements AfterViewInit, OnDestroy { } /** Private methods */ - private getConfig(config?:ModalOptions):ModalOptions { + private getConfig(config?: ModalOptions): ModalOptions { return Object.assign({}, modalConfigDefaults, config); } /** * Show dialog */ - private showElement(/*relatedTarget?:ViewContainerRef*/):void { + private showElement(/*relatedTarget?:ViewContainerRef*/): void { // todo: replace this with component helper usage `add to root` if (!this.element.nativeElement.parentNode || (this.element.nativeElement.parentNode.nodeType !== Node.ELEMENT_NODE)) { @@ -204,7 +202,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy { } } - private hideModal():void { + private hideModal(): void { this.renderer.setElementAttribute(this.element.nativeElement, 'aria-hidden', 'true'); this.renderer.setElementStyle(this.element.nativeElement, 'display', 'none'); this.showBackdrop(() => { @@ -218,14 +216,13 @@ export class ModalDirective implements AfterViewInit, OnDestroy { } // todo: original show was calling a callback when done, but we can use promise - private showBackdrop(callback?:Function):void { + private showBackdrop(callback?: Function): void { if (this._isShown && this.config.backdrop) { this.backdrop = this.componentsHelper .appendNextToRoot( ModalBackdropComponent, ModalBackdropOptions, - new ModalBackdropOptions({animate: false}), - this.injector); + new ModalBackdropOptions({animate: false})); if (this.isAnimated) { this.backdrop.instance.isAnimated = this.isAnimated; @@ -263,7 +260,7 @@ export class ModalDirective implements AfterViewInit, OnDestroy { } } - private removeBackdrop():void { + private removeBackdrop(): void { if (this.backdrop) { this.backdrop.destroy(); this.backdrop = void 0; @@ -295,19 +292,19 @@ export class ModalDirective implements AfterViewInit, OnDestroy { // } // } - private resetAdjustments():void { + private resetAdjustments(): void { this.renderer.setElementStyle(this.element.nativeElement, 'paddingLeft', ''); this.renderer.setElementStyle(this.element.nativeElement, 'paddingRight', ''); } /** Scroll bar tricks */ - private checkScrollbar():void { + private checkScrollbar(): void { this.isBodyOverflowing = this.document.body.clientWidth < window.innerWidth; this.scrollbarWidth = this.getScrollbarWidth(); } - private setScrollbar():void { + private setScrollbar(): void { if (!this.document) { return; } @@ -326,12 +323,12 @@ export class ModalDirective implements AfterViewInit, OnDestroy { } } - private resetScrollbar():void { + private resetScrollbar(): void { this.document.body.style.paddingRight = this.originalBodyPadding; } // thx d.walsh - private getScrollbarWidth():number { + private getScrollbarWidth(): number { const scrollDiv = this.renderer.createElement(this.document.body, 'div', void 0); scrollDiv.className = ClassName.SCROLLBAR_MEASURER; const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; diff --git a/components/utils/components-helper.service.ts b/components/utils/components-helper.service.ts index e4540a0247..744f8c24b8 100644 --- a/components/utils/components-helper.service.ts +++ b/components/utils/components-helper.service.ts @@ -38,19 +38,13 @@ export class ComponentsHelper { * ``` * @returns {ViewContainerRef} - application root view component ref */ - public getRootViewContainerRef(_injector:Injector):ViewContainerRef { + public getRootViewContainerRef():ViewContainerRef { // The only way for now (by @mhevery) // https://github.com/angular/angular/issues/6446#issuecomment-173459525 - // this is a class of application bootstrap component (like my-app) - const classOfRootComponent = this.applicationRef.componentTypes[0]; - // this is an instance of application bootstrap component - let appInstance:any; - let injector:any = _injector as any; - while (!appInstance) { - appInstance = injector.get(classOfRootComponent, false); - if (!appInstance && injector.parentInjector) { - injector = injector.parentInjector; - } + const appInstance = this.applicationRef.components[0].instance; + if (!appInstance.viewContainerRef) { + const appName = this.applicationRef.componentTypes[0].name; + throw new Error(`Missing 'viewContainerRef' declaration in ${appName} constructor`); } return appInstance.viewContainerRef; } @@ -88,14 +82,12 @@ export class ComponentsHelper { * @param ComponentClass - @Component class * @param ComponentOptionsClass - options class * @param options - instance of options - * @param contextInjector - injector to resolve root view container (any injector except root injector will fit) * @returns {ComponentRef} - returns ComponentRef */ public appendNextToRoot(ComponentClass:Type, ComponentOptionsClass:any, - options:any, - contextInjector:Injector):ComponentRef { - let location = this.getRootViewContainerRef(contextInjector); + options:any):ComponentRef { + let location = this.getRootViewContainerRef(); let providers = ReflectiveInjector.resolve([ {provide: ComponentOptionsClass, useValue: options} ]);