共计 5879 个字符,预计需要花费 15 分钟才能阅读完成。
动静渲染视图是日常我的项目开发中常见的需要,特地是通用性的工具库开发。例如弹窗服务,须要让组件的用户决定传入什么内容进行渲染,可能是一个组件,可能是一个模板(ng-template)。这里先来看看通过 Angular API 如何去实现。
要动静的插入一个视图,Angular 提供了一套 API 负责创立容器,实例化组件,以及治理视图和数据。同时,它还额定提供了一些指令,不便疾速的实现。它们各有各自实用场景,本篇具体来看看它们的应用。
残缺的示例代码能够查看 GitHub 我的项目仓库,或者在线查看成果 在线示例。
ViewContainerRef 容器
动静视图的外围是视图容器,它决定了视图的插入地位,用 ViewContainerRef 类示意。要创立它很简略,假如组件视图中有:<ng-container #dynamicHost></ng-container>
通过 @ViewChild
装璜器读取即可:
@ViewChild('dynamicHost', { read: ViewContainerRef}) container!: ViewContainerRef;
这段代码的留神点有:
@ViewChild
用来从组件视图中获取元素的援用,能够是 DOM 对象、子组件或者指令的实例、或是某个依赖注入的 Provider。
第二个参数中的 read 批示具体获取哪个类型,例如从 <button mat-button>
能够获取到这个 button 元素对象,也能够是这个标签上增加的 Material 按钮指令。
- 要获取一个容器,并非只能应用
<ng-container>
,<ng-template>
、组件或者别的 DOM 标签都能够。 - 尽管是叫容器,不过插入的内容可不是在这个
<ng-container>
标签内,而是在它的下方(相似<router-outlet>
)。所以应用 ng-container 是为了不渲染多余的 DOM。
ViewContainerRef 实例能够创立、插入、移除、挪动或是销毁它其中的视图(ViewRef)。视图代表着 Angular 中可显示元素的最小分组单位,它由组件或者模板定义。多个视图结构成了 Angular 利用的视图树。
✨扼要起见,后文都将 ViewContainerRef 实例称之为“视图容器”
以代码形式插入
视图容器有两个办法(createComponent,createEmbeddedView),用来动态创建组件视图和模板视图。
动静插入组件
首先创立一个用于动静插入的组件,这个告诉组件省略了具体款式,详情能够查看源码。能够留神到,这个组件有一个输出属性,一个输入事件。以便演示动态创建的组件,如何和外界交互。
@Component({
template: `
<div class="alert-container mat-elevation-z2" [class]="classConfig()">
<span class="message">{{message}}</span>
<button mat-button (click)="emitCloseEvent()"> 敞开 </button>
</div>`
})
export class AlertComponent {@Input() message = '空音讯提醒';
@Input() type = 'success';
@Output() closeAlert = new EventEmitter();
classConfig() {
return {
success: this.type === 'success',
warning: this.type === 'warning'
};
}
const
emitCloseEvent(): void {this.closeAlert.emit();
}
}
✨留神:Angular 9 后的版本默认应用 Ivy 编译器,如果是应用老版本编译器,这个须要动静插入的告诉组件,须要在 Module 的 entryComponents
中申明,并且这个 Module 不能懒加载。
告诉组件写好后,就能够创立并动静插入,具体分为三步:
- 在构造函数中注入
ComponentFactoryResolver
实例:private resolver: ComponentFactoryResolver
。 - 通过
resolver.resolveComponentFactory(AlertComponent)
办法,生成这个告诉组件的工厂对象。 - 最初一步,将这个工厂对象传入视图容器的 createComponent 办法:
this.const componentRef = container.createComponent(factory)
通过 createComponent 办法,就能够将这个组件插入到视图中了,并返回这个组件实例。
有了组件实例,和这个告诉组件交互也就不成问题了:
- 输出属性传值:
componentRef.instance.message = "内部传入的正告信息"
。 -
绑定输入事件:
componentRef.instance.closeAlert.subscribe(() => {const index = this.container.indexOf(componentRef.hostView); this.container.remove(index); });
从这个下面的绑定事件也能够看出,视图容器能够包容任意多个视图,依据视图对象能够查问索引,或者销毁,移除,插入,挪动任意视图程序。
动静插入模板
和 createComponent 相似的,通过 createEmbeddedView 就能够插入模板。
首先先创立一个模板示例,这个模板依据上下文对象申明了一个“param”属性:
<ng-template #templateView let-param="message">
<section class="template-wrapper">
<span> 来自 ng-template 的动静内容 </span>
<span>{{param}}</span>
</section>
</ng-template>
要插入一个带上下文数据的模板,具体分为三步:
-
获取模板的援用对象:
@ViewChild('templateView', { read: TemplateRef}) template!: TemplateRef<any>;
- 申明上下文对象:
templateContext = {message: '来自模板上下文的值'};
-
通过视图容器的 createEmbeddedView 办法插入模板:
const embeddedViewRef = this.container.createEmbeddedView(this.template, this.templateContext);
办法创立一个 EmbeddedViewRef 对象,并将它放入。通过这个对象,视图容器能够查找它的索引,所以也能够和之前组件视图的援用一样,在容器内挪动程序、移除渲染、或是被销毁。
以指令形式插入
除了应用视图容器的两个办法来创立和插入视图,Angular 还提供了两个指令来简化工作。
ngComponentOutlet
首先,再创立另一个示例组件,和后面的哪个告诉组件不同,它没有输入输出属性,然而多了一个须要注入的依赖项,以便演示带依赖注入的组件插入:
@Component({
template: `
<section class="template-wrapper">
<span> 来自另一个动静组件:{{param.message}}</span>
</section>`
})
export class AnotherComponent {constructor(public param: ExampleService) {}}
应用指令的形式创立组件就简略多了,只须要两步:
-
引入这个组件类,并赋值给一个属性:
import {AnotherComponent} from '../shared/another-component'; export class ViewContainerExampleComponent implements OnInit, OnDestroy {public anotherComponent = AnotherComponent;}
-
在视图中申明即可:
<ng-container *ngComponentOutlet="anotherComponent"></ng-container>
传入依赖注入器
失常状况下,这样就把组件插入指定地位了,不过如果动静组件所申明的依赖项,须要由这个组件自身提供呢?
这里就要再给这个组件传入注入对象: <ng-container *ngComponentOutlet="anotherComponent;injector:costumeInjector">
。
这个 costumeInjector 能够通过 Injector 类的静态方法创立:
constructor(injector: Injector) {
this.costumeInjector = Injector.create({providers: [{ provide: ExampleService, deps: [] }],
parent: injector
});
}
这样一来,每个动态创建的组件,都会领有一个独立的 ExampleService 实例。
✨ 视图容器的 createComponent 办法同样能够指定依赖注入器,成果是一样的,后面只是为了扼要而省略。
当然,常见的状况仍旧是给 ExampleService 的装璜器申明为全局服务:@Injectable({providedIn:'root'})
传入内容映射
除了能够指定注入器,还能够传入内容映射。
先给组件做一点小批改,新增一个 <ng-content>
标记,使得这个组件能够接管内部内容映射:
<section class="template-wrapper">
<span> 来自另一个动静组件:{{param.message}}</span>
<ng-content></ng-content>
</section>`
要插入映射的 DOM 内容,只须要额定给指令的表达式再传一个参数:<ng-container *ngComponentOutlet="anotherComponent;content:costumeContent">
。
这个 costumeContent 是一个数组,因为组件内能够有多个 ng-content
。数组内每项也是一个数组,因为每个 ng-content
地位,能够插入多个 DOM 内容块。
const spanContent = document.createElement('span');
const divContent = document.createElement('div');
spanContent.innerHTML = 'hello, world';
divContent.innerHTML = '<span>locotor</span>';
this.costumeContent = [[spanContent, divContent]];
ngTemplateOutlet
模板的指令只有两个输出属性:模板的援用对象、模板的上下文对象。
所以要插入一个带上下文数据的模板,具体步骤如下:
-
给模板增加援用名:
<ng-template #templateView let-param="message"> <!-- 省略内容 --> </ng-template>
- 申明上下文对象:
templateContext = {message: '来自模板上下文的值'};
-
传入 ngTemplateOutlet 指令中:
<ng-container *ngTemplateOutlet="templateView; context: templateContext"></ng-container>
比照一下
后面介绍了两种插入视图的形式,成果都是相似的,然而也有些许不同之处。理解它们的差别,能力依据场景应用适合的实现形式。
指令和 ViewContainerRef 对象实例的差别次要有两个:
- 多视图:相比指令的形式来插入视图,通过 ViewContainerRef 的创立办法,在一个视图容器中,能够创立任意多个视图。也因而,通过视图容器还具备对视图的治理能力,例如将某个视图移到容器的顶部,或是销毁某一个视图及其相干数据。
- 组件实例:通过 createComponent 办法插入组件视图的同时,还能够失去这个组件类的实例。有了它,就能够给组件传参,或是注册它的输入事件。
除了上述差别,其余的中央都是雷同的。比照一下插入组件时,应用指令的形式:
<ng-container *ngComponentOutlet="componentTypeExpression;
injector: injectorExpression;
content: contentNodesExpression;
ngModuleFactory: moduleFactory;">
</ng-container>
能够指定要插入组件类,组件的注入器,映射内容,以及模块工厂对象(容许动静加载其余模块)。
所对应的代码形式插入:
createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>
除了第二个参数是指定插入到容器的程序序号以外,其余的参数都是一一对应的。
和组件的状况相似,模板插入除了视图容器反对多个模板以外,能够反对指定插入序号外,和指令的形式齐全一样的。都是两个参数,一个是模板援用对象,一个是模板上下文对象。
总结
本篇总结了 Angular API 实现动静视图插入的形式。ViewContainerRef 能够反对任意多个视图的插入,对它们进行治理。它插入的组件能够拿到组件实例,可能执行输入输出交互。指令的形式能够便捷的插入视图,然而只能在一个容器内插入一个视图,对组件的输入输出交互反对有余。
能够看到,通过 Angular 原生 API 曾经能够实现动静视图性能,不过如果能联合指令式的便捷,再兼顾组件交互就好了。好在 Material 开发组还提供了一套 Angular CDK(组件开发套件),它的 Portal 模块,封装了原生 API,能够更不便的实现动静视图,咱们下篇见!😎