这篇文章我们将介绍在 Angular 中如何动态创建组件。
定义 AlertComponent 组件
首先,我们需要定义一个组件。
exe-alert.component.ts
import { Component, Input } from '@angular/core'; @Component({ selector: "exe-alert", template: ` <h1>Alert {{type}}</h1> `, }) export class AlertComponent { @Input() type: string = "success"; }
上面代码中,我们定义了一个简单的 alert 组件,该组件有一个输入属性 type ,用于让用户自定义提示的类型。我们的自定义组件最终是一个实际的 DOM 元素,因此如果我们需要在页面中插入该元素,我们就需要考虑在哪里放置该元素。
创建组件容器
在 Angular 中放置组件的地方称为 container 容器。接下来,我们将在 exe-app 组件中创建一个模板元素,此外我们使用模板变量的语法,声明一个模板变量。接下来模板元素 <ng-template> 将会作为我们的组件容器,具体示例如下:
app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'exe-app', template: ` <ng-template #alertContainer></ng-template> ` }) export class AppComponent { }
友情提示:容器可以是任意的 DOM 元素或组件。
在 AppComponent 组件中,我们可以通过 ViewChild 装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回的组件实例或相应的 DOM 元素,但这个示例中,我们需要获取 ViewContainerRef 实例。
ViewContainerRef 用于表示一个视图容器,可添加一个或多个视图。通过 ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。
根据以上需求,更新后的代码如下:
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; }
动态创建组件
接下来,在 AppComponent 组件中,我们来添加两个按钮,用于创建 AlertComponent 组件。
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; }
在我们定义 createComponent() 方法前,我们需要注入 ComponentFactoryResolver 服务对象。该 ComponentFactoryResolver 服务对象中,提供了一个很重要的方法 - resolveComponentFactory() ,该方法接收一个组件类作为参数,并返回 ComponentFactory 。
ComponentFactoryResolver 抽象类:
export abstract class ComponentFactoryResolver { static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver(); abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>; }
在 AppComponent 组件构造函数中,注入 ComponentFactoryResolver 服务:
constructor(private resolver: ComponentFactoryResolver) {}
接下来我们再来看一下 ComponentFactory 抽象类:
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>; }
通过观察 ComponentFactory 抽象类,我们知道可以通过调用 ComponentFactory 实例的 create() 方法,来创建组件。介绍完上面的知识,我们来实现 AppComponent 组件的 createComponent() 方法:
createComponent(type) { this.container.clear(); const factory: ComponentFactory = this.resolver.resolveComponentFactory(AlertComponent); this.componentRef: ComponentRef = this.container.createComponent(factory); }
接下来我们来分段解释一下上面的代码。
this.container.clear();
每次我们需要创建组件时,我们需要删除之前的视图,否则组件容器中会出现多个视图 (如果允许多个组件的话,就不需要执行清除操作 )。
复制代码 代码如下:
const factory: ComponentFactory = this.resolver.resolveComponentFactory(AlertComponent);
正如我们之前所说的,resolveComponentFactory() 方法接受一个组件并返回如何创建组件的 ComponentFactory 实例。
复制代码 代码如下:
this.componentRef: ComponentRef = this.container.createComponent(factory);