详解Angular 4.x 动态创建组件
动态创建组件
这篇文章我们将介绍在Angular中如何动态创建组件。
定义AlertComponent组件
首先,我们需要定义一个组件。
exe-alert.component.ts
import{Component,Input}from'@angular/core'; @Component({ selector:"exe-alert", template:`Alert{{type}}
`, }) exportclassAlertComponent{ @Input()type:string="success"; }
上面代码中,我们定义了一个简单的alert组件,该组件有一个输入属性type,用于让用户自定义提示的类型。我们的自定义组件最终是一个实际的DOM元素,因此如果我们需要在页面中插入该元素,我们就需要考虑在哪里放置该元素。
创建组件容器
在Angular中放置组件的地方称为container容器。接下来,我们将在exe-app组件中创建一个模板元素,此外我们使用模板变量的语法,声明一个模板变量。接下来模板元素
app.component.ts
import{Component}from'@angular/core'; @Component({ selector:'exe-app', template:`` }) exportclassAppComponent{}
友情提示:容器可以是任意的DOM元素或组件。
在AppComponent组件中,我们可以通过ViewChild装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回的组件实例或相应的DOM元素,但这个示例中,我们需要获取ViewContainerRef实例。
ViewContainerRef用于表示一个视图容器,可添加一个或多个视图。通过ViewContainerRef实例,我们可以基于TemplateRef实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef的主要作用是创建和管理内嵌视图或组件视图。
根据以上需求,更新后的代码如下:
import{Component,ViewChild,ViewContainerRef}from'@angular/core'; @Component({ selector:'exe-app', template:`` }) exportclassAppComponent{ @ViewChild("alertContainer",{read:ViewContainerRef})container:ViewContainerRef; }
动态创建组件
接下来,在AppComponent组件中,我们来添加两个按钮,用于创建AlertComponent组件。
import{Component,ViewChild,ViewContainerRef}from'@angular/core'; @Component({ selector:'exe-app', template:`` }) exportclassAppComponent{ @ViewChild("alertContainer",{read:ViewContainerRef})container:ViewContainerRef; }
在我们定义createComponent()方法前,我们需要注入ComponentFactoryResolver服务对象。该ComponentFactoryResolver服务对象中,提供了一个很重要的方法-resolveComponentFactory(),该方法接收一个组件类作为参数,并返回ComponentFactory。
ComponentFactoryResolver抽象类:
exportabstractclassComponentFactoryResolver{ staticNULL:ComponentFactoryResolver=new_NullComponentFactoryResolver(); abstractresolveComponentFactory(component:Type ):ComponentFactory ; }
在AppComponent组件构造函数中,注入ComponentFactoryResolver服务:
constructor(privateresolver:ComponentFactoryResolver){}
接下来我们再来看一下ComponentFactory抽象类:
exportabstractclassComponentFactory{ abstractgetselector():string; abstractgetcomponentType():Type ; //selectorforall elementsinthecomponent. abstractgetngContentSelectors():string[]; //theinputsofthecomponent. abstractgetinputs():{propName:string,templateName:string}[]; //theoutputsofthecomponent. abstractgetoutputs():{propName:string,templateName:string}[]; //Createsanewcomponent. abstractcreate( injector:Injector,projectableNodes?:any[][],rootSelectorOrNode?:string|any, ngModule?:NgModuleRef ):ComponentRef ; }
通过观察ComponentFactory抽象类,我们知道可以通过调用ComponentFactory实例的create()方法,来创建组件。介绍完上面的知识,我们来实现AppComponent组件的createComponent()方法:
createComponent(type){ this.container.clear(); constfactory:ComponentFactory= this.resolver.resolveComponentFactory(AlertComponent); this.componentRef:ComponentRef=this.container.createComponent(factory); }
接下来我们来分段解释一下上面的代码。
this.container.clear();
每次我们需要创建组件时,我们需要删除之前的视图,否则组件容器中会出现多个视图(如果允许多个组件的话,就不需要执行清除操作)。
constfactory:ComponentFactory=this.resolver.resolveComponentFactory(AlertComponent);
正如我们之前所说的,resolveComponentFactory()方法接受一个组件并返回如何创建组件的ComponentFactory实例。
this.componentRef:ComponentRef=this.container.createComponent(factory);
在上面代码中,我们调用容器的createComponent()方法,该方法内部将调用ComponentFactory实例的create()方法创建对应的组件,并将组件添加到我们的容器。
现在我们已经能获取新组件的引用,即可以我们可以设置组件的输入类型:
this.componentRef.instance.type=type;
同样我们也可以订阅组件的输出属性,具体如下:
this.componentRef.instance.output.subscribe(event=>console.log(event));
另外不能忘记销毁组件:
ngOnDestroy(){ this.componentRef.destroy(); }
最后我们需要将动态组件添加到NgModule的entryComponents属性中:
@NgModule({ ..., declarations:[AppComponent,AlertComponent], bootstrap:[AppComponent], entryComponents:[AlertComponent], }) exportclassAppModule{}
完整示例
exe-alert.component.ts
import{Component,Input,Output,EventEmitter}from'@angular/core'; @Component({ selector:"exe-alert", template:`Alert{{type}}
`, }) exportclassAlertComponent{ @Input()type:string="success"; @Output()output=newEventEmitter(); }
app.component.ts
import{ Component,ViewChild,ViewContainerRef,ComponentFactory, ComponentRef,ComponentFactoryResolver,OnDestroy }from'@angular/core'; import{AlertComponent}from'./exe-alert.component'; @Component({ selector:'exe-app', template:`` }) exportclassAppComponentimplementsOnDestroy{ componentRef:ComponentRef ; @ViewChild("alertContainer",{read:ViewContainerRef})container:ViewContainerRef; constructor(privateresolver:ComponentFactoryResolver){} createComponent(type:string){ this.container.clear(); constfactory: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{NgModule,CUSTOM_ELEMENTS_SCHEMA}from'@angular/core'; import{BrowserModule}from'@angular/platform-browser'; import{AppComponent}from'./app.component'; import{AlertComponent}from'./exe-alert.component'; @NgModule({ imports:[BrowserModule], declarations:[AppComponent,AlertComponent], bootstrap:[AppComponent], entryComponents:[AlertComponent], schemas:[CUSTOM_ELEMENTS_SCHEMA] }) exportclassAppModule{}
总结
-
获取装载动态组件的容器
-
在组件类的构造函数中,注入ComponentFactoryResolver对象
-
调用ComponentFactoryResolver对象的resolveComponentFactory()方法创建ComponentFactory对象
-
调用组件容器对象的createComponent()方法创建组件并自动添加动态组件到组件容器中
-
基于返回的ComponentRef组件实例,配置组件相关属性(可选)
-
在模块Metadata对象的entryComponents属性中添加动态组件
-
declarations-用于指定属于该模块的指令和管道列表
-
entryComponents-用于指定在模块定义时,需要编译的组件列表。对于列表中声明的每个组件,Angular将会创建对应的一个ComponentFactory对象,并将其存储在ComponentFactoryResolver对象中
-
我有话说
通常情况下,当我们使用结构指令时,我们需要添加额外的标签来封装内容,如使用*ngIf指令:
Divone
Divtwo
上面示例中,我们在section标签上应用了ngIf指令,从而实现section标签内容的动态显示。这种方式有个问题是,我们必须添加额外的DOM元素。要解决该问题,我们可以使用
Divone
Divtwo
问题是解决了但我们不再使用*语法糖语法,这样会导致我们代码的不统一。虽然解决了问题,但又带来了新问题。那我们还有其它的方案么?答案是有的,我们可以使用ng-container指令。
Divone
Divtwo
有时我们需要根据switch语句,动态显示文本,这时我们需要添加一个额外的标签如,具体示例如下:
Textone Texttwo
针对这种情况,理论上我们是不需要添加额外的标签,这时我们可以使用ng-container来解决这个问题:
Textone Texttwo
介绍完ng-container指令,我们来分析一下它跟ng-template指令有什么区别?我们先看以下示例:
Intemplate,noattributes.
Inng-container,noattributes.
以上代码运行后,浏览器中输出结果是:
Inng-container,noattributes.
即
ngIfwithatemplate.
ngIfwithanng-container.
以上代码运行后,浏览器中输出结果是:
ngIfwithatemplate.
ngIfwithanng-container.
现在我们来总结一下
:使用*语法糖的结构指令,最终都会转换为 或模板指令,模板内的内容如果不进行处理,是不会在页面中显示的。
:是一个逻辑容器,可用于对节点进行分组,但不作为DOM树中的节点,它将被渲染为HTML中的comment元素,它可用于避免添加额外的元素来使用结构指令。
最后再来看一个
模板定义
Title
Content
渲染结果
Title
Content
TemplateRef与ViewContainerRef有什么作用?
TemplateRef
用于表示内嵌的template模板元素,通过TemplateRef实例,我们可以方便创建内嵌视图(EmbeddedViews),且可以轻松地访问到通过ElementRef封装后的nativeElement。需要注意的是组件视图中的template模板元素,经过渲染后会被替换成comment元素。
ViewContainerRef
用于表示一个视图容器,可添加一个或多个视图。通ViewContainerRef实例,我们可以基于TemplateRef实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef的主要作用是创建和管理内嵌视图或组件视图。(本示例就是通过ViewContainerRef对象提供的API来动态地创建组件视图)。
ViewChild装饰器还支持哪些查询条件?
ViewChild装饰器用于获取模板视图中的元素,它支持Type类型或string类型的选择器,同时支持设置read查询条件,以获取不同类型的实例。
exportinterfaceViewChildDecorator{ //Type类型:@ViewChild(ChildComponent) //string类型:@ViewChild('tpl',{read:ViewContainerRef}) (selector:Type|Function|string,{read}?:{read?:any}):any; new(selector:Type |Function|string, {read}?:{read?:any}):ViewChild; }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。