PopupService in ng-bootstrap
It is used to load the component dynamically, resolve string content or templateRef.
Use the content to replace <ng-content>
in the target component.
In the end, returns the new created component’s reference.
Constructor
private _windowFactory: ComponentFactory<T>;
constructor(
type: any, private _injector: Injector, private _viewContainerRef: ViewContainerRef, private _renderer: Renderer,
componentFactoryResolver: ComponentFactoryResolver) {
this._windowFactory = componentFactoryResolver.resolveComponentFactory<T>(type);
}
type
is aComponent Type
that will be rendered in the pop-up window.injector
is provided by the host component.viewContainerRef
is provided by the caller that refer to the host component.renderer
is used to manage DOM elements in UI-
componentFactoryResolver
is provided by host component that is used to load component dynamically windowFactory
is created for loading component later.
ContentRef
A container to hold elements nodes
and needed reference
ContentRef Class
export class ContentRef {
constructor(public nodes: any[], public viewRef?: ViewRef, public componentRef?: ComponentRef<any>) {}
}
- nodes - elements to be added into
. Text nodes created by Renderer when content is string or nodes created by createEmbeddedView method when content is a template reference - viewRef - the view reference created by createEmbeddedView method if the content is template
- componentRef - not used in PopupService
ContentRef will be created when opening a pop-up window
private _getContentRef(content: string | TemplateRef<any>, context?: any): ContentRef {
if (!content) {
return new ContentRef([]);
} else if (content instanceof TemplateRef) {
const viewRef = this._viewContainerRef.createEmbeddedView(<TemplateRef<T>>content, context);
return new ContentRef([viewRef.rootNodes], viewRef);
} else {
return new ContentRef([[this._renderer.createText(null, `${content}`)]]);
}
}
- Depends on the type of
content
, createContentRef instance
in different ways. - When content is empty, create an empty ContentRef
- When content is a
TemplateRef
, insert the template into host view container with_viewContainerRef.createEmbeddedView
. And save theelement nodes
andview reference
in ContentRef
- When content is just a string, create standalone test element, and surround the element to match the needed format.
Opening the pop-up window
open(content?: string | TemplateRef<any>, context?: any): ComponentRef<T> {
if (!this._windowRef) {
this._contentRef = this._getContentRef(content, context);
this._windowRef =
this._viewContainerRef.createComponent(this._windowFactory, 0, this._injector, this._contentRef.nodes);
}
return this._windowRef;
}
If window haven’t been created, create the ContentRef instance and then load component dynamically with _viewContainerRef.createComponent
this._viewContainerRef.createComponent(this._windowFactory, 0, this._injector, this._contentRef.nodes);
nodes
parameter will replacein the dynamically loaded component. - It returns the reference to the loaded component.
- Create the component next to the component where we got
_viewContainerRef
If the content is a templateRef, PopupService only copy contents in template into ng-conent
(without ng-content, nodes will exist behind new loaded component)
<button class="btn" (click)="clickButton()">Button</button>
<ng-template>
This is content in template!
</ng-template>
@ViewChild(TemplateRef) templateRef: TemplateRef<any>;
clickButton() {
this.popupService.open(this.templateRef);
}
When closing the pop-up window, remove created view from host container.
close() {
if (this._windowRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._windowRef.hostView));
this._windowRef = null;
if (this._contentRef.viewRef) { // when creating pop-up with template
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef));
this._contentRef = null;
}
}
}
The source
import {
Injector,
TemplateRef,
ViewRef,
ViewContainerRef,
Renderer,
ComponentRef,
ComponentFactory,
ComponentFactoryResolver
} from '@angular/core';
export class ContentRef {
constructor(public nodes: any[], public viewRef?: ViewRef, public componentRef?: ComponentRef<any>) {}
}
export class PopupService<T> {
private _windowFactory: ComponentFactory<T>;
private _windowRef: ComponentRef<T>;
private _contentRef: ContentRef;
constructor(
type: any, private _injector: Injector, private _viewContainerRef: ViewContainerRef, private _renderer: Renderer,
componentFactoryResolver: ComponentFactoryResolver) {
this._windowFactory = componentFactoryResolver.resolveComponentFactory<T>(type);
}
open(content?: string | TemplateRef<any>, context?: any): ComponentRef<T> {
if (!this._windowRef) {
this._contentRef = this._getContentRef(content, context);
this._windowRef =
this._viewContainerRef.createComponent(this._windowFactory, 0, this._injector, this._contentRef.nodes);
}
return this._windowRef;
}
close() {
if (this._windowRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._windowRef.hostView));
this._windowRef = null;
if (this._contentRef.viewRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._contentRef.viewRef));
this._contentRef = null;
}
}
}
private _getContentRef(content: string | TemplateRef<any>, context?: any): ContentRef {
if (!content) {
return new ContentRef([]);
} else if (content instanceof TemplateRef) {
const viewRef = this._viewContainerRef.createEmbeddedView(<TemplateRef<T>>content, context);
return new ContentRef([viewRef.rootNodes], viewRef);
} else {
return new ContentRef([[this._renderer.createText(null, `${content}`)]]);
}
}
}