UNPKG

@progress/kendo-angular-popup

Version:

Kendo UI Angular Popup component - an easily customized popup from the most trusted provider of professional Angular components.

279 lines (278 loc) 10.4 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ApplicationRef, ComponentFactoryResolver, ElementRef, InjectionToken, Injectable, Injector, Inject, Optional, TemplateRef } from '@angular/core'; import { PopupComponent } from './popup.component'; import * as i0 from "@angular/core"; const removeElement = (element) => { if (element && element.parentNode) { element.parentNode.removeChild(element); } }; /** * Used to inject the Popup container. If not provided, the first root component of * the application is used. * * > The `POPUP_CONTAINER` can be used only with the [`PopupService`](slug:service_popup) class. * * In case you are using standalone components: * * @example * * ```ts * import { Component } from '@angular/core'; * import { KENDO_POPUP, PopupService } from '@progress/kendo-angular-popup'; * * @Component({ * standalone: true, * imports: [KENDO_POPUP], * providers: [PopupService, { * provide: POPUP_CONTAINER, * useFactory: () => { * //return the container ElementRef, where the popup will be injected * return { nativeElement: document.body } as ElementRef; * } * }], * selector: 'app-root', * templateUrl: './app.component.html', * }) * export class AppComponent {} * ``` * * In case you are using an NgModule-based application: * * @example * * ```ts-no-run * import { PopupModule, POPUP_CONTAINER } from '@progress/kendo-angular-popup'; * import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; * import { ElementRef, NgModule } from '@angular/core'; * import { AppComponent } from './app.component'; * * _@NgModule({ * declarations: [AppComponent], // declare app component * imports: [BrowserModule, PopupModule], // import Popup module * bootstrap: [AppComponent], * providers: [{ * provide: POPUP_CONTAINER, * useFactory: () => { * //return the container ElementRef, where the popup will be injected * return { nativeElement: document.body } as ElementRef; * } * }] * }) * export class AppModule {} * * platformBrowserDynamic().bootstrapModule(AppModule); * ``` */ export const POPUP_CONTAINER = new InjectionToken('Popup Container'); /** * A service for opening Popup components dynamically * ([see example]({% slug service_popup %})). * * @export * @class PopupService */ export class PopupService { applicationRef; componentFactoryResolver; injector; container; /** * Gets the root view container into which the component will be injected. * * @returns {ComponentRef<any>} */ get rootViewContainer() { // https://github.com/angular/angular/blob/4.0.x/packages/core/src/application_ref.ts#L571 const rootComponents = this.applicationRef.components || []; if (rootComponents[0]) { return rootComponents[0]; } throw new Error(` View Container not found! Inject the POPUP_CONTAINER or define a specific ViewContainerRef via the appendTo option. See https://www.telerik.com/kendo-angular-ui/components/popup/api/POPUP_CONTAINER/ for more details. `); } /** * Sets or gets the HTML element of the root component container. * * @returns {HTMLElement} */ get rootViewContainerNode() { return this.container ? this.container.nativeElement : this.getComponentRootNode(this.rootViewContainer); } constructor(applicationRef, componentFactoryResolver, injector, container) { this.applicationRef = applicationRef; this.componentFactoryResolver = componentFactoryResolver; this.injector = injector; this.container = container; } /** * Opens a Popup component. Created Popups are mounted * in the DOM directly in the root application component. * * @param {PopupSettings} options - The options which define the Popup. * @returns {ComponentRef<PopupComponent>} - A reference to the Popup object. * * @example * * ```ts-no-run * _@Component({ * selector: 'my-app', * template: ` * <ng-template #template> * Popup content * </ng-template> * <button #anchor kendoButton (click)="open(anchor, template)">Open</button> * ` * }) * export class AppComponent { * public popupRef: PopupRef; * * constructor( private popupService: PopupService ) {} * * public open(anchor: ElementRef, template: TemplateRef<any>): void { * if (this.popupRef) { * this.popupRef.close(); * this.popupRef = null; * return; * } * * this.popupRef = this.popupService.open({ * anchor: anchor, * content: template * }); * } * } * ``` */ open(options = {}) { const { component, nodes } = this.contentFrom(options.content); const popupComponentRef = this.appendPopup(nodes, options.appendTo); const popupInstance = popupComponentRef.instance; this.projectComponentInputs(popupComponentRef, options); popupComponentRef.changeDetectorRef.detectChanges(); if (component) { component.changeDetectorRef.detectChanges(); } const popupElement = this.getComponentRootNode(popupComponentRef); return { close: () => { if (component) { component.destroy(); } popupComponentRef.destroy(); // Issue in Chrome causes https://github.com/telerik/kendo-angular/issues/4434 // To be fixed in Chrome, remove try..catch afterwards // https://chromestatus.com/feature/5128696823545856 // https://issues.chromium.org/issues/41484175 try { // Angular will not remove the element unless the change detection is triggered removeElement(popupElement); } catch { /* noop */ } }, content: component, popup: popupComponentRef, popupAnchorViewportLeave: popupInstance.anchorViewportLeave, popupClose: popupInstance.close, popupElement: popupElement, popupOpen: popupInstance.open, popupPositionChange: popupInstance.positionChange }; } appendPopup(nodes, container) { const popupComponentRef = this.createComponent(PopupComponent, nodes, container); if (!container) { this.rootViewContainerNode.appendChild(this.getComponentRootNode(popupComponentRef)); } return popupComponentRef; } /** * Gets the HTML element for a component reference. * * @param {ComponentRef<any>} componentRef * @returns {HTMLElement} */ getComponentRootNode(componentRef) { return componentRef.location.nativeElement; } /** * Gets the `ComponentFactory` instance by its type. * * @param {*} componentClass * @param {*} nodes * @returns {ComponentRef<any>} */ getComponentFactory(componentClass) { return this.componentFactoryResolver.resolveComponentFactory(componentClass); } /** * Creates a component reference from a `Component` type class. * * @param {*} componentClass * @param {*} nodes * @returns {ComponentRef<any>} */ createComponent(componentClass, nodes, container) { const factory = this.getComponentFactory(componentClass); if (container) { return container.createComponent(factory, undefined, this.injector, nodes); } else { const component = factory.create(this.injector, nodes); this.applicationRef.attachView(component.hostView); return component; } } /** * Projects the inputs on the component. * * @param {ComponentRef<any>} component * @param {*} options * @returns {ComponentRef<any>} */ projectComponentInputs(component, options) { Object.getOwnPropertyNames(options) .filter(prop => prop !== 'content' || options.content instanceof TemplateRef) .map((prop) => { component.instance[prop] = options[prop]; }); return component; } /** * Gets the component and the nodes to append from the `content` option. * * @param {*} content * @returns {any} */ contentFrom(content) { if (!content || content instanceof TemplateRef) { return { component: null, nodes: [[]] }; } const component = this.createComponent(content); const nodes = component ? [component.location.nativeElement] : []; return { component: component, nodes: [ nodes // <ng-content> ] }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopupService, deps: [{ token: i0.ApplicationRef }, { token: i0.ComponentFactoryResolver }, { token: i0.Injector }, { token: POPUP_CONTAINER, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopupService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: PopupService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: i0.ApplicationRef }, { type: i0.ComponentFactoryResolver }, { type: i0.Injector }, { type: i0.ElementRef, decorators: [{ type: Inject, args: [POPUP_CONTAINER] }, { type: Optional }] }]; } });