import { ComponentRef, Injectable, TemplateRef, EventEmitter, Renderer2, RendererFactory2 } from '@angular/core'; import { ComponentLoader } from '../component-loader/component-loader.class'; import { ComponentLoaderFactory } from '../component-loader/component-loader.factory'; import { ModalBackdropComponent } from './modal-backdrop.component'; import { ModalContainerComponent } from './modal-container.component'; import { CLASS_NAME, modalConfigDefaults, ModalOptions, TRANSITION_DURATIONS } from './modal-options.class'; import { BsModalRef } from './bs-modal-ref.service'; @Injectable() export class BsModalService { // constructor props config: ModalOptions = modalConfigDefaults; onShow: EventEmitter = new EventEmitter(); onShown: EventEmitter = new EventEmitter(); onHide: EventEmitter = new EventEmitter(); onHidden: EventEmitter = new EventEmitter(); protected isBodyOverflowing = false; protected originalBodyPadding = 0; protected scrollbarWidth = 0; protected backdropRef: ComponentRef; private _backdropLoader: ComponentLoader; private modalsCount = 0; private lastDismissReason = ''; private loaders: ComponentLoader[] = []; private _renderer: Renderer2; constructor(rendererFactory: RendererFactory2, private clf: ComponentLoaderFactory) { this._backdropLoader = this.clf.createLoader( null, null, null ); this._renderer = rendererFactory.createRenderer(null, null); } /** Shows a modal */ show(content: string | TemplateRef | any, config?: ModalOptions): BsModalRef { this.modalsCount++; this._createLoaders(); this.config = Object.assign({}, modalConfigDefaults, config); this._showBackdrop(); this.lastDismissReason = null; return this._showModal(content); } hide(level: number) { if (this.modalsCount === 1) { this._hideBackdrop(); this.resetScrollbar(); } this.modalsCount = this.modalsCount >= 1 ? this.modalsCount - 1 : 0; setTimeout(() => { this._hideModal(level); this.removeLoaders(level); }, this.config.animated ? TRANSITION_DURATIONS.BACKDROP : 0); } _showBackdrop(): void { const isBackdropEnabled = this.config.backdrop || this.config.backdrop === 'static'; const isBackdropInDOM = !this.backdropRef || !this.backdropRef.instance.isShown; if (this.modalsCount === 1) { this.removeBackdrop(); if (isBackdropEnabled && isBackdropInDOM) { this._backdropLoader .attach(ModalBackdropComponent) .to('body') .show({ isAnimated: this.config.animated }); this.backdropRef = this._backdropLoader._componentRef; } } } _hideBackdrop(): void { if (!this.backdropRef) { return; } this.backdropRef.instance.isShown = false; const duration = this.config.animated ? TRANSITION_DURATIONS.BACKDROP : 0; setTimeout(() => this.removeBackdrop(), duration); } _showModal(content: any): BsModalRef { const modalLoader = this.loaders[this.loaders.length - 1]; const bsModalRef = new BsModalRef(); const modalContainerRef = modalLoader .provide({ provide: ModalOptions, useValue: this.config }) .provide({ provide: BsModalRef, useValue: bsModalRef }) .attach(ModalContainerComponent) .to('body') .show({content, isAnimated: this.config.animated, initialState: this.config.initialState, bsModalService: this}); modalContainerRef.instance.level = this.getModalsCount(); bsModalRef.hide = () => { modalContainerRef.instance.hide(); }; bsModalRef.content = modalLoader.getInnerComponent() || null; return bsModalRef; } _hideModal(level: number): void { const modalLoader = this.loaders[level - 1]; if (modalLoader) { modalLoader.hide(); } } getModalsCount(): number { return this.modalsCount; } setDismissReason(reason: string) { this.lastDismissReason = reason; } removeBackdrop(): void { this._backdropLoader.hide(); this.backdropRef = null; } /** AFTER PR MERGE MODAL.COMPONENT WILL BE USING THIS CODE */ /** Scroll bar tricks */ /** @internal */ checkScrollbar(): void { this.isBodyOverflowing = document.body.clientWidth < window.innerWidth; this.scrollbarWidth = this.getScrollbarWidth(); } setScrollbar(): void { if (!document) { return; } this.originalBodyPadding = parseInt( window .getComputedStyle(document.body) .getPropertyValue('padding-right') || '0', 10 ); if (this.isBodyOverflowing) { document.body.style.paddingRight = `${this.originalBodyPadding + this.scrollbarWidth}px`; } } private resetScrollbar(): void { document.body.style.paddingRight = `${this.originalBodyPadding}px`; } // thx d.walsh private getScrollbarWidth(): number { const scrollDiv = this._renderer.createElement('div'); this._renderer.addClass(scrollDiv, CLASS_NAME.SCROLLBAR_MEASURER); this._renderer.appendChild(document.body, scrollDiv); const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; this._renderer.removeChild(document.body, scrollDiv); return scrollbarWidth; } private _createLoaders(): void { const loader = this.clf.createLoader( null, null, null ); this.copyEvent(loader.onBeforeShow, this.onShow); this.copyEvent(loader.onShown, this.onShown); this.copyEvent(loader.onBeforeHide, this.onHide); this.copyEvent(loader.onHidden, this.onHidden); this.loaders.push(loader); } private removeLoaders(level: number): void { this.loaders.splice(level - 1, 1); this.loaders.forEach( (loader: ComponentLoader, i: number) => { loader.instance.level = i + 1; } ); } private copyEvent(from: EventEmitter, to: EventEmitter) { from.subscribe(() => { to.emit(this.lastDismissReason); }); } }