Basic
angular支持动态创建component,即在运行时动态创建component并将其加载到对应的dom中;而不是像通常情况下使用标签将component固定的添加到某个template中。动态创建组件的过程中涉及到两种component:
- 将要被动态创建出来的component
- 作为动态插入component载体的父component
动态创建出的component
动态创建的component与普通的组件基本没有区别,同样是包含template,style等,可以省略selector属性。
如果该组建只用来动态创建,而不使用标签的形似固定的插入到dom中,那么可以省略selector属性。
以下我们创建一个简单的message组件,内容比较简单,采用内联template的写法,该组建接受一个input,并在click时向父组件传递信息:
// message.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
// 此处省略了selector和style
template: `
<div (click)='outEve.emit(message+ ‘as output’)'>{{message}}</div>
`
})
export class MessageComponent {
@Input() message: string;
@Output() outEve = new EventEmitter();
}
parent component - 即用来动态插入component的父节点
而在父组件中,也就是动态生成的地方,我们要考虑以下几个问题:
1. 在哪里加载组件,或者说生成组件首先需要有一个容器
-
可以将任何dom元素当作组建的容器,通常情况下建议使用标签,因为该标签不会在dom中添加任何额外的元素,可以将其看作一个隐形的容器
<ng-template #container></ng-template> -
通过@ViewChild装饰器,从模板中获取该容器,它支持read查询条件,可以返回不同的实例。
如使用{ read: ElementRef }获取元素的ElementRef类型的实例如下图:
@ViewChild('container', { read: ElementRef }) containerEleRef;
而此处,需要获取ViewContainerRef类型的实例,如下:
@ViewChild('container', { read: ViewContainerRef }) container;
通过ViewContainerRef类可以获取容器视图的访问权,这个容器就是用来加载动态组件的宿主。
2. 如何根据子组件类,在上述容器中生成组件
-
使用ComponentFactoryResolver类动态加载组件,该类中包含一个resolveComponentFactory方法,该方法能够为组件解析出一个ComponentFactory类。而ComponentFactory类中的create方法能够创建真正的component。或者可以简单的理解为ComponentFactory类是一个知道如何创建component 的对象。
首先,在构造函数中注入ComponentFactoryResolver类:
import { ComponentFactoryResolver} from '@angular/core'; constructor(private factoryResolver: ComponentFactoryResolver) { }然后, 通过ComponentFactoryResolver的resolveComponentFactory解析出Message组件的ComponentFactory类,ComponentFactory会为MessageComponent创建一个实例:
const factory = this.factoryResolver.resolveComponentFactory(MessageComponent);然后,调用ViewContainerRef类的createComponent的方法,将创建出来的组件实例添加到模板中,createComponent()方法返回一个引用,指向刚刚加载的组件:
let componentRef = this.container.createComponent(factory);
3. 如何处理子组件的input和output
-
可以通过上述组件的引用获取组件实例,并处理其input及output 回调等:
this.componentRef.instance.message = 'any message'; this.componentRef.instance.outEve.subscribe(res => console.log(res));
4. 将动态加载的组件添加到NgModule的entryComponents中,确保编译器能够正常的生成工厂类。
@NgModule({
declarations: [
...
],
entryComponents:[
MessageComponent
],
imports: [
MessageComponent,
DynamicParentComponent,
...
],
providers: [
...
]
Parent container component 完整代码:
// dynamic-parent.component.ts
import { Component, OnInit, ComponentFactoryResolver, ComponentRef, ViewChild, ViewContainerRef, OnDestroy } from '@angular/core';
import { MessageComponent } from './message.component';
@Component({
selector: 'app-dynamic-parent',
template: `
<button (click)="createDynamicComponent('success')">create success message component</button>
<button (click)="createDynamicComponent('warning')">create warning message component</button>
<ng-template #container></ng-template>
`
})
export class DynamicParentComponent implements OnInit, OnDestroy {
@ViewChild('container', { read: ViewContainerRef }) container;
private componentRef: ComponentRef<any>;
constructor(private factoryResolver: ComponentFactoryResolver) { }
ngOnInit(): void {
}
createDynamicComponent(type: string) {
this.container.clear();
const factory = this.factoryResolver.resolveComponentFactory(MessageComponent);
this.componentRef = this.container.createComponent(factory);
this.componentRef.instance.message = type;
this.componentRef.instance.outEve.subscribe(res => console.log(res));
}
ngOnDestroy() {
this.componentRef.destroy();
}
}