动态创建组件
这篇文章我们将介绍在 Angular 中如何动态创建组件。
定义 AlertComponent 组件
首先,我们需要定义一个组件。
exe-alert.component.ts
selector: "exe-alert",template: `
Alert {{type}}
`,}) export class AlertComponent { @Input() type: string = "success"; }上面代码中,我们定义了一个简单的 alert
组件,该组件有一个输入属性 type
,用于让用户自定义提示的类型。我们的自定义组件最终是一个实际的 DOM 元素,因此如果我们需要在页面中插入该元素,我们就需要考虑在哪里放置该元素。
创建组件容器
在 Angular 中放置组件的地方称为 container
容器。接下来,我们将在 exe-app
组件中创建一个模板元素,此外我们使用模板变量的语法,声明一个模板变量。接下来模板元素
将会作为我们的组件容器,具体示例如下:
app.component.ts
selector: 'exe-app',template:
<ng-template #alertContainer></ng-template>
})
export class AppComponent { }
友情提示:容器可以是任意的 DOM 元素或组件。
在 AppComponent 组件中,我们可以通过 ViewChild
装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回的组件实例或相应的 DOM 元素,但这个示例中,我们需要获取 ViewContainerRef
实例。
ViewContainerRef 用于表示一个视图容器,可添加一个或多个视图。通过 ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。
根据以上需求,更新后的代码如下:
selector: 'exe-app',template:
<ng-template #alertContainer></ng-template>
})
export class AppComponent {
@ViewChild("alertContainer",{ read: ViewContainerRef }) container: ViewContainerRef;
}
动态创建组件
接下来,在 AppComponent 组件中,我们来添加两个按钮,用于创建 AlertComponent 组件。
在我们定义 createComponent()
方法前,我们需要注入 ComponentFactoryResolver
服务对象。该 ComponentFactoryResolver
服务对象中,提供了一个很重要的方法 - resolveComponentFactory()
,该方法接收一个组件类作为参数,并返回 ComponentFactory
。
ComponentFactoryResolver 抽象类:
在 AppComponent 组件构造函数中,注入 ComponentFactoryResolver 服务:
接下来我们再来看一下 ComponentFactory 抽象类:
// selector for all
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
}
通过观察 ComponentFactory 抽象类,我们知道可以通过调用 ComponentFactory 实例的 create()
方法,来创建组件。介绍完上面的知识,我们来实现 AppComponent 组件的 createComponent()
方法:
接下来我们来分段解释一下上面的代码。
每次我们需要创建组件时,我们需要删除之前的视图,否则组件容器中会出现多个视图 (如果允许多个组件的话,就不需要执行清除操作 )。
正如我们之前所说的,resolveComponentFactory()
方法接受一个组件并返回如何创建组件的 ComponentFactory
实例。
在上面代码中,我们调用容器的 createComponent()
方法,该方法内部将调用 ComponentFactory
实例的 create() 方法创建对应的组件,并将组件添加到我们的容器。
现在我们已经能获取新组件的引用,即可以我们可以设置组件的输入类型:
另外不能忘记销毁组件:
最后我们需要将动态组件添加到 NgModule 的 entryComponents 属性中:
完整示例
exe-alert.component.ts
selector: "exe-alert",template:
<h1 (click)="output.next(type)">Alert {{type}}</h1>
,})export class AlertComponent {
@Input() type: string = "success";
@Output() output = new EventEmitter();
}
app.component.ts
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
@ViewChild("alertContainer",{ read: ViewContainerRef }) container: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) { }
createComponent(type: string) {
this.container.clear();
const factory: ComponentFactory
this.resolver.resolveComponentFactory(AlertComponent);
this.componentRef = this.container.createComponent(factory);
this.componentRef.instance.type = type;
this.componentRef.instance.output.subscribe((msg: string) => console.log(msg));
}
ngOnDestroy() {
this.componentRef.destroy()
}
}
app.module.ts
import { AlertComponent } from './exe-alert.component';
@NgModule({
imports: [BrowserModule],schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }
总结
-
获取装载动态组件的容器
-
在组件类的构造函数中,注入
ComponentFactoryResolver
对象 -
调用
ComponentFactoryResolver
对象的resolveComponentFactory()
方法创建ComponentFactory
对象 -
基于返回的
ComponentRef
组件实例,配置组件相关属性 (可选) -
在模块 Metadata 对象的 entryComponents 属性中添加动态组件
-
declarations - 用于指定属于该模块的指令和管道列表
-
entryComponents - 用于指定在模块定义时,需要编译的组件列表。对于列表中声明的每个组件,Angular 将会创建对应的一个 ComponentFactory 对象,并将其存储在 ComponentFactoryResolver 对象中
-
我有话说
与
有什么区别?
通常情况下,当我们使用结构指令时,我们需要添加额外的标签来封装内容,如使用 *ngIf
指令:
Div one
Div two
上面示例中,我们在 section 标签上应用了 ngIf 指令,从而实现 section 标签内容的动态显示。这种方式有个问题是,我们必须添加额外的 DOM 元素。要解决该问题,我们可以使用
Div one
Div two
问题是解决了但我们不再使用 *
语法糖语法,这样会导致我们代码的不统一。虽然解决了问题,但又带来了新问题。那我们还有其它的方案么?答案是有的,我们可以使用 ng-container
指令。
是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment
元素。使用
的示例如下: