[Anglar]-使用ComponentFactoryResolver动态产生Component
Angular提供了ComponentFactoryResolver,来协助我们在项目中动态实现Component,而不是把页面需要的所有的组件都写道view里,根据不同的条件来判断显示目标component,当遇到程序现实复杂的需求时非常好用,写出的代码易理解也易维护。今天我们就来看看如何透过ComponentFactoryResolver来动态产生需要的Component。
需求说明
首先看看以下画面,我们希望点选radio时,可以依照不同的选择切换不同的Component。
先不考虑动态产生,只有3个component时,代码可以很简单通过ngIf来判断component是否要被产生,可读性也不至于太差。
1 <input type="radio" id="showComponentA" name="showComponent" value="componentA" [(ngModel)]="selectedComponentName"/> 2 <label for="showComponentA">组件 A</label> 3 4 <input type="radio" id="showComponentB" name="showComponent" value="componentB" [(ngModel)]="selectedComponentName"/> 5 <label for="showComponentB">组件 B</label> 6 7 <input type="radio" id="showComponentC" name="showComponent" value="componentC" [(ngModel)]="selectedComponentName"/> 8 <label for="showComponentC">组件 C</label> 9 10 <app-component-a *ngIf="selectedComponentName === 'componentA'"></app-component-a> 11 <app-component-b *ngIf="selectedComponentName === 'componentB'"></app-component-b> 12 <app-component-c *ngIf="selectedComponentName === 'componentC'"></app-component-c>
不过当选想变得很多,或者可选项通过后端决定等等较为复杂的情况时,代码很容易变得杂乱不好维护。身为优秀的程序员,自然不希望这种情况发生,因此我们需要通过动态的方式,来产生component。也就是今天的主角--ComponentFactoryResolver。
建立DynamicComponentDirective
首先我们先建立一个directive,并注入ViewContainerRef,ViewContainerRef可以让我们知道目前所在的HTML元素包含的view内容,也可以通过它来改变view的结果(例如:动态的产生component,或者动态的移除component等等)。
执行命令: ng generate directive DynamicComponent
1 import { Directive, ViewContainerRef } from '@angular/core'; 2 3 @Directive({ 4 selector: '[appDynamicComponent]' 5 }) 6 export class DynamicComponentDirective { 7 8 constructor(public viewcontainerRef: ViewContainerRef) { } 9 10 }
接着我们需要套用这个directive到需要动态产生component的容器上,我们可以简单地套用ng-template就好,把原来的view代码改成如下
1 <input type="radio" id="showComponentA" name="showComponent" value="componentA" (change)="displayComponent('componentA')"/> 2 <label for="showComponentA">组件 A</label> 3 4 <input type="radio" id="showComponentB" name="showComponent" value="componentB" (change)="displayComponent('componentB')"/> 5 <label for="showComponentB">组件 B</label> 6 7 <input type="radio" id="showComponentC" name="showComponent" value="componentC" (change)="displayComponent('componentC')"/> 8 <label for="showComponentC">组件 C</label> 9 10 <ng-template appDynamicComponent></ng-template>
原来的3行components就浓缩成只剩下一行了,同时我们也不用看到一堆脏脏的ngIf了,view的呈现顿时清爽可许多;同时我们替radiobox加入change事件,要决定动态产生哪一个component,而接下来就是动态产生的重头戏。
使用ComponentFactoryResolver动态产生Component
1 import { Component, ViewChild, ComponentFactoryResolver } from '@angular/core'; 2 import { DynamicComponentDirective } from './dynamic-component.directive'; 3 import { DynamicComponentService } from './dynamic-component.service'; 4 5 @Component({ 6 selector: 'app-root', 7 templateUrl: './app.component.html', 8 styleUrls: ['./app.component.css'], 9 providers: [DynamicComponentService] 10 }) 11 export class AppComponent { 12 @ViewChild(DynamicComponentDirective) componentHost: DynamicComponentDirective; 13 constructor(private dynamicComponentService: DynamicComponentService, 14 private componentFactoryResolver: ComponentFactoryResolver) { 15 } 16 displayComponent(componentName: string) { 17 const componentFactory = this.componentFactoryResolver.resolveComponentFactory( 18 this.dynamicComponentService.getComponent(componentName) 19 ); 20 const viewContainerRef = this.componentHost.viewcontainerRef; 21 viewContainerRef.clear(); 22 viewContainerRef.createComponent(componentFactory); 23 } 24 }
在这边我们做了这样几件事:
1.使用ViewChild取得要动态创建component的directive(componentHost)
2.注入ComponentFactoryResolver
3.在displayComponent中,使用ComponentFactoryResolver.ResolveComponentFactory来创建一个ComponentFactory
4.通过componentHost的ViewContainerRef,将内容清空(viewcontainerRef.clear())
5.通过viewcontainerRef.creatComponent(componentFactory),产生我们需要的component并放入componetHost之中
至于 如何产生哪一个component,这里我们额外建立了一个DynamicComponentService服务,来决定产生哪一个component
执行命令: ng generate service DynamicComponent
1 import { Injectable } from '@angular/core'; 2 import { ComponentAComponent } from './component-a/component-a.component'; 3 import { ComponentBComponent } from './component-b/component-b.component'; 4 import { ComponentCComponent } from './component-c/component-c.component'; 5 6 @Injectable({ 7 providedIn: 'root' 8 }) 9 export class DynamicComponentService { 10 private components = { 11 componentA: ComponentAComponent, 12 componentB: ComponentBComponent, 13 componentC: ComponentCComponent 14 }; 15 constructor() { } 16 17 getComponent(componentName: string) { 18 return this.components[componentName]; 19 } 20 }
在Module加入entryComponents
接下来就是最后一步了,由于我们的Component是动态产生,而不是直接透过View上的selector产生的,为了确保能够产生动态的Component,我们还需要在所属的Module中加入一个entryComponents阵列
1 import { BrowserModule } from '@angular/platform-browser'; 2 import { NgModule } from '@angular/core'; 3 import { FormsModule } from '@angular/forms'; 4 5 import { AppComponent } from './app.component'; 6 import { ComponentAComponent } from './component-a/component-a.component'; 7 import { ComponentBComponent } from './component-b/component-b.component'; 8 import { ComponentCComponent } from './component-c/component-c.component'; 9 import { DynamicComponentDirective } from './dynamic-component.directive'; 10 11 @NgModule({ 12 declarations: [ 13 AppComponent, 14 ComponentAComponent, 15 ComponentBComponent, 16 ComponentCComponent, 17 DynamicComponentDirective 18 ], 19 imports: [ 20 BrowserModule, 21 FormsModule 22 ], 23 providers: [], 24 bootstrap: [AppComponent], 25 entryComponents: [ 26 ComponentAComponent, 27 ComponentBComponent, 28 ComponentCComponent 29 ] 30 }) 31 export class AppModule { }
完整代码地址:https://github.com/1weidong/component-factory-resolver/tree/master
基于vue3最新标准,实现后台前端综合解决方案 + 前端技术书籍 原价368, 限时39.9
qq649149488