背景
组件的模板不会永远是固定的。应用可能会需要在运行期间加载一些新的组件。比如:英雄管理局正在计划一个广告活动,要在广告条中显示一系列不同的广告。几个不同的小组可能会频繁加入新的广告组件。 再用只支持静态组件结构的模板显然是不现实的。你需要一种新的组件加载方式,它不需要在广告条组件的模板中引用固定的组件。
先看看效果图:
准备工作
我们以angular为例来做一次尝试。首先我们抽象地考虑一下,动态组件加载需要哪些东西?首先你要加载的组件们应该定义出来,其次你需要一个可以挂载这些动态组件的容器,并且要考虑怎么把你的组件挂载到容器上。
定义组件很容易,我们这里定义两个最简单的组件:
动态主键1:
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-dynamic1',
template: `<p style="background-color: chartreuse;">dynamic1 works!{{data}}</p>`,
styleUrls: ['./dynamic1.component.less']
})
export class Dynamic1Component implements OnInit {
@Input() data: number;
constructor() { }
ngOnInit() {
}
}
动态主键2:
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-dynamic2',
template: `<p style="background-color: crimson;">dynamic2 works!</p>`,
styleUrls: ['./dynamic2.component.less']
})
export class Dynamic2Component implements OnInit {
constructor() { }
ngOnInit() {
}
}
接着我们在appModule里添加对应的component
@NgModule({
declarations: [
AppComponent,
TestcomponentComponent,
Dynamic1Component,
Dynamic2Component,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule
],
entryComponents:[
Dynamic1Component,
Dynamic2Component,
],//动态组件设置,在编译阶段不会实例化,运行时实例化
providers: [],
bootstrap: [AppComponent]
})
实现
在html代码里我们简单定义一个容器:ng-template就是装载组件的容器
<button (click)="addComponent()">Add Component</button>
<button (click)="stopSwitch()">Stop</button>
<ng-template #dmroom></ng-template>
接着我们来实现加载动态组件:
- 通过viewchild引入容器的模板变:@ViewChild('dmroom', { read: ViewContainerRef,static:true}) dmRoom: ViewContainerRef,其中{read: ContainerRef}是说把这个节点当作ContainerRef进行解释,因为ContainerRef可以去挂载组件。
- 通过依赖注入实例化组件工厂加工器: constructor(private cfr: ComponentFactoryResolver) { },其中我们依赖注入了ComponentFactoryResolver,用它来获取动态组件的工厂,然后ContainerRef调用createComponent完成两件事:1.利用动态组件的工厂创建出动态组件的实例,2. 把动态组件的实例挂载到ContainerRef中。
代码实现:
export class AppComponent {
@ViewChild('dmroom', { read: ViewContainerRef,static:true}) dmRoom: ViewContainerRef;
currentIndex: number = -1;
interval: any;
components:Array<any>=[Dynamic1Component,Dynamic2Component]
constructor(private cfr: ComponentFactoryResolver) { }
addComponent(){
this.interval=setInterval(()=>{
this.currentIndex=(this.currentIndex+1)%this.components.length
let comp=this.components[this.currentIndex]
let com = this.cfr.resolveComponentFactory(comp);
this.dmRoom.clear()
let componentRef =this.dmRoom.createComponent(com);
(componentRef.instance).data=5
},2000)
}
stopSwitch() {
clearInterval(this.interval);
//this.dmRoom.clear()
}
ngOnDestroy() {
clearInterval(this.interval);
}
}
我们这个组件用一个定时器去不断循环加载我们定义的两个动态组件,用@ViewChild获取模板中的<ng-template>节点作为加载的容器
-
this.dmRoom.clear()是每次加载新的组件时先清空之前加载的容器
-
let componentRef =this.dmRoom.createComponent(com);是创建容器实例,返回一个容器实例
- (componentRef.instance).data=5;componentRef.instance相当于实例化当前的component类实例,定义input属性就可以传入值进行绑定
-
ngOnDestroy() { clearInterval(this.interval) }销毁组件时清理定时器