/** * Copyright 2020 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Injectable, Renderer2, RendererFactory2, Inject, ComponentFactoryResolver, Injector, ApplicationRef, EmbeddedViewRef, ComponentRef, EventEmitter, Type, OnDestroy } from '@angular/core'; import { DOCUMENT } from '@angular/common'; @Injectable({ providedIn: 'root' }) export class DialogService implements OnDestroy { private readonly renderer: Renderer2; private dialog: HTMLElement; private backdrop: HTMLElement; private readonly componentCache = new Map<Type<any>, ComponentRef<void>>(); constructor( rendererFactory: RendererFactory2, private readonly resolver: ComponentFactoryResolver, private readonly injector: Injector, private readonly appRef: ApplicationRef, @Inject(DOCUMENT) private readonly document: HTMLDocument ) { this.renderer = rendererFactory.createRenderer(null, null); this.handleCloseDialog = this.handleCloseDialog.bind(this); } open(component: Type<any>) { let componentRef = this.componentCache.get(component); if (!componentRef) { const componentFactory = this.resolver.resolveComponentFactory(component); componentRef = componentFactory.create(this.injector); this.componentCache.set(component, componentRef); this.appRef.attachView(componentRef.hostView); const closeDialog = (componentRef.instance as any).closeDialog; if (closeDialog && closeDialog instanceof EventEmitter) { (closeDialog as EventEmitter<void>).subscribe(() => { this.close(); }); } } if (!this.backdrop) { this.backdrop = this.document.getElementById('app-dialog-backdrop'); } if (!this.dialog) { this.dialog = this.document.getElementById('app-dialog'); this.dialog.addEventListener('close-dialog', this.handleCloseDialog, true); } this.renderer.addClass(this.backdrop, 'is-open'); this.dialog.innerHTML = ''; this.renderer.appendChild(this.dialog, (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0]); } close() { if (!this.backdrop) { return; } this.renderer.removeClass(this.backdrop, 'is-open'); this.dialog.dispatchEvent(new CustomEvent('dialog-closed')); } handleCloseDialog() { this.close(); } ngOnDestroy() { if (this.dialog) { this.dialog.removeEventListener('close-dialog', this.handleCloseDialog); } } }