@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
JavaScript
/**-----------------------------------------------------------------------------------------
* 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
}] }]; } });