Dynamically create components
In this article we will introduce how to dynamically create components in Angular.
Define the AlertComponent component
First, we need to define a component.
import { Component, Input } from '@angular/core'; @Component({ selector: "exe-alert", template: ` <h1>Alert {{type}}</h1> `, }) export class AlertComponent { @Input() type: string = "success"; }
In the above code, we define a simplealert
Component, this component has an input propertytype
, used to let the user customize the type of prompt. Our custom component ends up being an actual DOM element, so if we need to insert that element into the page, we need to consider where to place it.
Create component containers
Where components are placed in Angular are calledcontainer
container. Next, we will exe-app
Create a template element in the component, and in addition, we use the syntax of the template variable to declare a template variable. Next template elements<ng-template>
It will be used as our component container, the specific examples are as follows:
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <ng-template #alertContainer></ng-template> ` }) export class AppComponent { }
Friendly tip: The container can be any DOM element or component.
In the AppComponent component, we can useViewChild
Decorator to get the template element in the view. If no second query parameter is specified, the component instance or corresponding DOM element will be returned by default, but in this example, we need to getViewContainerRef
Example.
ViewContainerRef is used to represent a view container that can add one or more views. Through the ViewContainerRef instance, we can create embedded views based on the TemplateRef instance, and specify the insertion location of the embedded views, and can also facilitate management of existing views in the view container. In short, the main function of ViewContainerRef is to create and manage embedded views or component views.
According to the above requirements, the updated code is as follows:
import { Component, ViewChild, ViewContainerRef } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <ng-template #alertContainer></ng-template> ` }) export class AppComponent { @ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef; }
Dynamically create components
Next, in the AppComponent component, we will add two buttons to create the AlertComponent component.
import { Component, ViewChild, ViewContainerRef } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <ng-template #alertContainer></ng-template> <button (click)="createComponent('success')">Create success alert</button> <button (click)="createComponent('danger')">Create danger alert</button> ` }) export class AppComponent { @ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef; }
In our definition createComponent()
Before the method, we need to inject ComponentFactoryResolver
Service object. ShouldComponentFactoryResolver
A very important method is provided among the service objects- resolveComponentFactory()
, this method receives a component class as a parameter and returnsComponentFactory
。
ComponentFactoryResolver abstract class:
export abstract class ComponentFactoryResolver { static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver(); abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>; }
In the AppComponent component constructor, inject the ComponentFactoryResolver service:
constructor(private resolver: ComponentFactoryResolver) {}
Next, let’s take a look at the ComponentFactory abstract class:
export abstract class ComponentFactory<C> { abstract get selector(): string; abstract get componentType(): Type<any>; // selector for all <ng-content> elements in the component. abstract get ngContentSelectors(): string[]; // the inputs of the component. abstract get inputs(): {propName: string, templateName: string}[]; // the outputs of the component. abstract get outputs(): {propName: string, templateName: string}[]; // Creates a new component. abstract create( injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string|any, ngModule?: NgModuleRef<any>): ComponentRef<C>; }
By observing the ComponentFactory abstract class, we know that we can call the ComponentFactory instance create()
method to create components. After introducing the above knowledge, let’s implement the AppComponent componentcreateComponent()
method:
createComponent(type) { (); const factory: ComponentFactory = (AlertComponent); : ComponentRef = (factory); }
Next, let’s explain the above code in paragraphs.
();
Every time we need to create a component, we need to delete the previous view, otherwise multiple views will appear in the component container (if multiple components are allowed, there is no need to perform a clear operation).
const factory: ComponentFactory = (AlertComponent);
As we said before,resolveComponentFactory()
Method accepts a component and returns how to create a component ComponentFactory
Example.
: ComponentRef = (factory);
In the above code, we call the container's createComponent()
method, which will be called internally ComponentFactory
The create() method of the instance creates the corresponding component and adds the component to our container.
Now that we can get a reference to the new component, we can set the input type of the component:
= type;
Similarly, we can subscribe to the output properties of the component, as follows:
(event => (event));
Also, you must not forget to destroy components:
ngOnDestroy() { (); }
Finally, we need to add the dynamic component to the entryComponents property of NgModule:
@NgModule({ ..., declarations: [AppComponent, AlertComponent], bootstrap: [AppComponent], entryComponents: [AlertComponent], }) export class AppModule { }
Complete example
import { Component, Input, Output, EventEmitter } from '@angular/core'; @Component({ selector: "exe-alert", template: ` <h1 (click)="(type)">Alert {{type}}</h1> `, }) export class AlertComponent { @Input() type: string = "success"; @Output() output = new EventEmitter(); }
import { Component, ViewChild, ViewContainerRef, ComponentFactory, ComponentRef, ComponentFactoryResolver, OnDestroy } from '@angular/core'; import { AlertComponent } from './'; @Component({ selector: 'exe-app', template: ` <ng-template #alertContainer></ng-template> <button (click)="createComponent('success')">Create success alert</button> <button (click)="createComponent('danger')">Create danger alert</button> ` }) export class AppComponent implements OnDestroy { componentRef: ComponentRef<AlertComponent>; @ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef; constructor(private resolver: ComponentFactoryResolver) { } createComponent(type: string) { (); const factory: ComponentFactory<AlertComponent> = (AlertComponent); = (factory); = type; ((msg: string) => (msg)); } ngOnDestroy() { () } }
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './'; import { AlertComponent } from './'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent, AlertComponent], bootstrap: [AppComponent], entryComponents: [AlertComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class AppModule { }
Summarize
-
Get the container that loads dynamic components
-
In the constructor of the component class, inject
ComponentFactoryResolver
Object -
Call
ComponentFactoryResolver
The object'sresolveComponentFactory()
Method creationComponentFactory
Object -
Calling component container object
createComponent()
Methods to create components and automatically add dynamic components to component containers -
Based on return
ComponentRef
Component instance, configure component-related properties (optional) -
Add dynamic components to the entryComponents property of the module Metadata object
-
declarations - Used to specify a list of instructions and pipelines belonging to the module
-
entryComponents - Used to specify a list of components that need to be compiled when the module is defined. For each component declared in the list, Angular will create a corresponding ComponentFactory object and store it in the ComponentFactoryResolver object.
-
I have something to say
<ng-template>
and<ng-container>
What's the difference?
Normally, when we use structure instructions, we need to add additional tags to encapsulate the content, such as *ngIf
instruction:
<section *ngIf="show"> <div> <h2>Div one</h2> </div> <div> <h2>Div two</h2> </div> </section>
In the above example, we applied the ngIf directive on the section tag to realize the dynamic display of the section tag content. One problem with this approach is that we have to add additional DOM elements. To solve this problem, we can use the standard syntax of <ng-template> (non-*ngIf syntax sugar):
<ng-template [ngIf]="show"> <div> <h2>Div one</h2> </div> <div> <h2>Div two</h2> </div> </ng-template>
The problem is solved but we no longer use it*
Syntax sugar syntax, which will lead to inconsistency in our code. Although the problem was solved, it brought about new problems. So do we have other plans? There is a answer, we can use itng-container
instruction.
<ng-container>
<ng-container>
is a logical container that can be used to group nodes, but is not a node in the DOM tree, it will be rendered as an HTMLcomment
element. use <ng-container>
Examples are as follows:
<ng-container *ngIf="show"> <div> <h2>Div one</h2> </div> <div> <h2>Div two</h2> </div> </ng-container>
Sometimes we need toswitch
Statement, dynamically display text, at this time we need to add an additional tag such as <span>
, the specific examples are as follows:
<div [ngSwitch]="value"> <span *ngSwitchCase="0">Text one</span> <span *ngSwitchCase="1">Text two</span> </div>
In this case, in theory we don't need to add extra <span>
Tags, we can use ng-container
To solve this problem:
<div [ngSwitch]="value"> <ng-container *ngSwitchCase="0">Text one</ng-container> <ng-container *ngSwitchCase="1">Text two</ng-container> </div>
Finished introduction ng-container
Let's analyze the instructions and ng-template
What is the difference between instructions? Let's look at the following example:
<ng-template> <p> In template, no attributes. </p> </ng-template> <ng-container> <p> In ng-container, no attributes. </p> </ng-container>
After the above code is run, the output result in the browser is:
In ng-container, no attributes.
Right now <ng-template>
The content in it will not be displayed. When added in the above template ngIf
instruction:
<template [ngIf]="true"> <p> ngIf with a template.</p> </template> <ng-container *ngIf="true"> <p> ngIf with an ng-container.</p> </ng-container>
After the above code is run, the output result in the browser is:
ngIf with a template.
ngIf with an ng-container.
Now let's summarize it <ng-template>
and <ng-container>
Differences:
- <ng-template>: Use * syntax sugar structure directives and will eventually be converted into <ng-template> or <template> template directives. If the content in the template is not processed, it will not be displayed on the page.
- <ng-container>: is a logical container that can be used to group nodes, but not as a node in the DOM tree. It will be rendered as a comment element in HTML, which can be used to avoid adding additional elements to use structural directives.
Let's see one last<ng-container>
Example of usage:
Template definition
<div> <ng-container *ngIf="true"> <h2>Title</h2> <div>Content</div> </ng-container> </div>
Rendering results
<div> <!--bindings={ "ng-reflect-ng-if": "true" }--><!----> <h2>Title</h2> <div>Content</div> </div>
What are the functions of TemplateRef and ViewContainerRef?
TemplateRef
Used to represent embedded template elements, through the TemplateRef instance, we can easily create embedded views (Embedded Views) and easily access nativeElement encapsulated by ElementRef. It should be noted that the template template element in the component view will be replaced with the comment element after rendering.
ViewContainerRef
Used to represent a view container, one or more views can be added. Through the ViewContainerRef instance, we can create embedded views based on the TemplateRef instance, and specify the insertion location of the embedded views, and can also facilitate management of existing views in the view container. In short, the main function of ViewContainerRef is to create and manage embedded views or component views. (This example is to dynamically create component views through the API provided by the ViewContainerRef object).
What other query conditions does the ViewChild decorator support?
The ViewChild decorator is used to get elements in the template view. It supports selectors of Type or string type, and also supports setting read query conditions to get instances of different types.
export interface ViewChildDecorator { // Type type: @ViewChild(ChildComponent) // string type: @ViewChild('tpl', { read: ViewContainerRef }) (selector: Type<any>|Function|string, {read}?: {read?: any}): any; new (selector: Type<any>|Function|string, {read}?: {read?: any}): ViewChild; }
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.