SoFunction
Updated on 2025-04-08

Detailed explanation of Angular dynamic component creation

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).

Copy the codeThe code is as follows:

const factory: ComponentFactory = (AlertComponent);

As we said before,resolveComponentFactory() Method accepts a component and returns how to create a component ComponentFactory Example.

Copy the codeThe code is as follows:

: 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, injectComponentFactoryResolverObject

  • CallComponentFactoryResolverThe object'sresolveComponentFactory()Method creationComponentFactoryObject

  • Calling component container objectcreateComponent()Methods to create components and automatically add dynamic components to component containers

  • Based on returnComponentRefComponent 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:

  1. <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.
  2. <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&lt;any&gt;|Function|string, {read}?: {read?: any}): any;

 new (selector: Type&lt;any&gt;|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.