import { ApplicationRef , ComponentFactory , ComponentFactoryResolver , ComponentRef , EmbeddedViewRef , Injectable , Injector , Type , ViewContainerRef , } from "@angular/core" ; import { first } from "rxjs/operators" ; import { DynamicModalComponent } from "../components/modal/dynamic-modal.component" ; import { ModalInjector } from "../components/modal/modal-injector" ; import { ModalRef } from "../components/modal/modal.ref" ; export class ModalConfig < D = any > { data ? : D ; allowMultipleModals : boolean = false ; } @ Injectable ( ) export class ModalService { protected modalList : ComponentRef < DynamicModalComponent > [ ] = [ ] ; private factoryResolvers : Map < Type < any > , ComponentFactoryResolver > = new Map ( ) ; constructor ( private componentFactoryResolver , private applicationRef , private injector ) { document . addEventListener ( "keyup" , ( event ) => { if ( event . key === "Escape" && this . modalCount > 0 ) { this . topModal . instance . close ( ) ; } } ) ; } get modalCount ( ) { return this . modalList . length ; } private get topModal ( ) { return this . modalList [ this . modalCount - 1 ] ; } async openViewRef < T > ( componentType , viewContainerRef , setComponentParameters = null ) { const [ modalRef , modalComponentRef ] = this . openInternal ( componentType , null , false ) ; modalComponentRef . instance . setComponentParameters = setComponentParameters ; viewContainerRef . insert ( modalComponentRef . hostView ) ; await modalRef . onCreated . pipe ( first ( ) ) . toPromise ( ) ; return [ modalRef , modalComponentRef . instance . componentRef . instance ] ; } open ( componentType , config ? ) { if ( ! ( config ?. allowMultipleModals ?? false ) && this . modalCount > 0 ) { return ; } const [ modalRef , _ ] = this . openInternal ( componentType , config , true ) ; return modalRef ; } registerComponentFactoryResolver < T > ( componentType , componentFactoryResolver ) { this . factoryResolvers . set ( componentType , componentFactoryResolver ) ; } resolveComponentFactory < T > ( componentType ) { if ( this . factoryResolvers . has ( componentType ) ) { return this . factoryResolvers . get ( componentType ) . resolveComponentFactory ( componentType ) ; } return this . componentFactoryResolver . resolveComponentFactory ( componentType ) ; } protected openInternal ( componentType , config ? , attachToDom ? ) { const [ modalRef , componentRef ] = this . createModalComponent ( config ) ; componentRef . instance . childComponentType = componentType ; if ( attachToDom ) { this . applicationRef . attachView ( componentRef . hostView ) ; const domElem = ( componentRef . hostView as EmbeddedViewRef < any > ) . rootNodes [ 0 ] as HTMLElement ; document . body . appendChild ( domElem ) ; } modalRef . onClosed . pipe ( first ( ) ) . subscribe ( ( ) => { if ( attachToDom ) { this . applicationRef . detachView ( componentRef . hostView ) ; } componentRef . destroy ( ) ; this . modalList . pop ( ) ; if ( this . modalCount > 0 ) { this . topModal . instance . getFocus ( ) ; } } ) ; this . setupHandlers ( modalRef ) ; this . modalList . push ( componentRef ) ; return [ modalRef , componentRef ] ; } protected setupHandlers ( modalRef ) { let backdrop = null ; modalRef . onCreated . pipe ( first ( ) ) . subscribe ( ( el ) => { document . body . classList . add ( "modal-open" ) ; const modalEl = el . querySelector ( ".modal" ) ; const dialogEl = modalEl . querySelector ( ".modal-dialog" ) as HTMLElement ; backdrop = document . createElement ( "div" ) ; backdrop . className = "modal-backdrop fade" ; backdrop . style . zIndex = `${ this . modalCount }040` ; modalEl . prepend ( backdrop ) ; dialogEl . addEventListener ( "click" , ( e ) => { e . stopPropagation ( ) ; } ) ; dialogEl . style . zIndex = `${ this . modalCount }050` ; const modals = Array . from ( el . querySelectorAll ( '.modal-backdrop, .modal *[data-dismiss="modal"]' ) ) ; for ( const closeElement of modals ) { closeElement . addEventListener ( "click" , ( event ) => { modalRef . close ( ) ; } ) ; } } ) ; modalRef . onClose . pipe ( first ( ) ) . subscribe ( ( ) => { modalRef . closed ( ) ; if ( this . modalCount === 0 ) { document . body . classList . remove ( "modal-open" ) ; } } ) ; } protected createModalComponent ( config ) { const modalRef = new ModalRef ( ) ; const map = new WeakMap ( ) ; map . set ( ModalConfig , config ) ; map . set ( ModalRef , modalRef ) ; const componentFactory = this . componentFactoryResolver . resolveComponentFactory ( DynamicModalComponent ) ; const componentRef = componentFactory . create ( new ModalInjector ( this . injector , map ) ) ; return [ modalRef , componentRef ] ; } }