动态创建组件
这篇文章我们将介绍在 Angular 中如何动态创建组件。
定义 AlertComponent 组件
首先,我们需要定义一个组件。
exe-alert.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: "exe-alert",
template: `
<h1>Alert {{type}}</h1>
`,
})
export class AlertComponent {
@Input() type: string = "success";
}
上面代码中,我们定义了一个简单的 alert 组件,该组件有一个输入属性 type ,用于让用户自定义提示的类型。我们的自定义组件最终是一个实际的 DOM 元素,因此如果我们需要在页面中插入该元素,我们就需要考虑在哪里放置该元素。
创建组件容器
在 Angular 中放置组件的地方称为 container 容器。接下来,我们将在 exe-app 组件中创建一个模板元素,此外我们使用模板变量的语法,声明一个模板变量。接下来模板元素 <ng-template> 将会作为我们的组件容器,具体示例如下:
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'exe-app',
template: `
<ng-template #alertContainer></ng-template>
`
})
export class AppComponent { }
友情提示:容器可以是任意的 DOM 元素或组件。
在 AppComponent 组件中,我们可以通过 ViewChild 装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回的组件实例或相应的 DOM 元素,但这个示例中,我们需要获取 ViewContainerRef 实例。
ViewContainerRef 用于表示一个视图容器,可添加一个或多个视图。通过 ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。
根据以上需求,更新后的代码如下:
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'exe-app',
template: `
<ng-template #alertContainer></ng-template>
`
})
export class AppComponent {
@ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef;
}
动态创建组件
接下来,在 AppComponent 组件中,我们来添加两个按钮,用于创建 AlertComponent 组件。
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
selector: 'exe-app',
template: `
<ng-template #alertContainer></ng-template>
<button (click)="createComponent('success')">Create success alert</button>
<button (click)="createComponent('danger')">Create danger alert</button>
`
})
export class AppComponent {
@ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef;
}
在我们定义 createComponent() 方法前,我们需要注入 ComponentFactoryResolver 服务对象。该 ComponentFactoryResolver 服务对象中,提供了一个很重要的方法 - resolveComponentFactory() ,该方法接收一个组件类作为参数,并返回 ComponentFactory 。
ComponentFactoryResolver 抽象类:
export abstract class ComponentFactoryResolver {
static NULL: ComponentFactoryResolver = new _NullComponentFactoryResolver();
abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>;
}
在 AppComponent 组件构造函数中,注入 ComponentFactoryResolver 服务:
constructor(private resolver: ComponentFactoryResolver) {}
接下来我们再来看一下 ComponentFactory 抽象类:
export abstract class ComponentFactory<C> {
abstract get selector(): string;
abstract get componentType(): Type<any>;
// selector for all <ng-content> elements in the component.
abstract get ngContentSelectors(): string[];
// the inputs of the component.
abstract get inputs(): {propName: string, templateName: string}[];
// the outputs of the component.
abstract get outputs(): {propName: string, templateName: string}[];
// Creates a new component.
abstract create(
injector: Injector, projectableNodes"htmlcode">
createComponent(type) {
this.container.clear();
const factory: ComponentFactory =
this.resolver.resolveComponentFactory(AlertComponent);
this.componentRef: ComponentRef = this.container.createComponent(factory);
}
接下来我们来分段解释一下上面的代码。
this.container.clear();
每次我们需要创建组件时,我们需要删除之前的视图,否则组件容器中会出现多个视图 (如果允许多个组件的话,就不需要执行清除操作 )。
复制代码 代码如下:
const factory: 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],
})
export class AppModule { }
完整示例
exe-alert.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: "exe-alert",
template: `
<h1 (click)="output.next(type)">Alert {{type}}</h1>
`,
})
export class AlertComponent {
@Input() type: string = "success";
@Output() output = new EventEmitter();
}
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: `
<ng-template #alertContainer></ng-template>
<button (click)="createComponent('success')">Create success alert</button>
<button (click)="createComponent('danger')">Create danger alert</button>
`
})
export class AppComponent implements OnDestroy {
componentRef: ComponentRef<AlertComponent>;
@ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) { }
createComponent(type: string) {
this.container.clear();
const factory: ComponentFactory<AlertComponent> =
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]
})
export class AppModule { }
总结
-
获取装载动态组件的容器
-
在组件类的构造函数中,注入
ComponentFactoryResolver对象 -
调用
ComponentFactoryResolver对象的resolveComponentFactory()方法创建ComponentFactory对象 -
调用组件容器对象的
createComponent()方法创建组件并自动添加动态组件到组件容器中 -
基于返回的
ComponentRef组件实例,配置组件相关属性 (可选) -
在模块 Metadata 对象的 entryComponents 属性中添加动态组件
-
declarations - 用于指定属于该模块的指令和管道列表
-
entryComponents - 用于指定在模块定义时,需要编译的组件列表。对于列表中声明的每个组件,Angular 将会创建对应的一个 ComponentFactory 对象,并将其存储在 ComponentFactoryResolver 对象中
-
我有话说
<ng-template> 与 <ng-container> 有什么区别?
通常情况下,当我们使用结构指令时,我们需要添加额外的标签来封装内容,如使用 *ngIf 指令:
<section *ngIf="show"> <div> <h2>Div one</h2> </div> <div> <h2>Div two</h2> </div> </section>
上面示例中,我们在 section 标签上应用了 ngIf 指令,从而实现 section 标签内容的动态显示。这种方式有个问题是,我们必须添加额外的 DOM 元素。要解决该问题,我们可以使用 <ng-template> 的标准语法 (非*ngIf语法糖):
<ng-template [ngIf]="show"> <div> <h2>Div one</h2> </div> <div> <h2>Div two</h2> </div> </ng-template>
问题是解决了但我们不再使用 * 语法糖语法,这样会导致我们代码的不统一。虽然解决了问题,但又带来了新问题。那我们还有其它的方案么?答案是有的,我们可以使用 ng-container 指令。
<ng-container>
<ng-container> 是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment 元素。使用 <ng-container> 的示例如下:
<ng-container *ngIf="show"> <div> <h2>Div one</h2> </div> <div> <h2>Div two</h2> </div> </ng-container>
有时我们需要根据 switch 语句,动态显示文本,这时我们需要添加一个额外的标签如 <span> ,具体示例如下:
<div [ngSwitch]="value"> <span *ngSwitchCase="0">Text one</span> <span *ngSwitchCase="1">Text two</span> </div>
针对这种情况,理论上我们是不需要添加额外的 <span> 标签,这时我们可以使用 ng-container 来解决这个问题:
<div [ngSwitch]="value"> <ng-container *ngSwitchCase="0">Text one</ng-container> <ng-container *ngSwitchCase="1">Text two</ng-container> </div>
介绍完 ng-container 指令,我们来分析一下它跟 ng-template 指令有什么区别?我们先看以下示例:
<ng-template> <p> In template, no attributes. </p> </ng-template> <ng-container> <p> In ng-container, no attributes. </p> </ng-container>
以上代码运行后,浏览器中输出结果是:
In ng-container, no attributes.
即 <ng-template> 中的内容不会显示。当在上面的模板中添加 ngIf 指令:
<template [ngIf]="true"> <p> ngIf with a template.</p> </template> <ng-container *ngIf="true"> <p> ngIf with an ng-container.</p> </ng-container>
以上代码运行后,浏览器中输出结果是:
ngIf with a template.
ngIf with an ng-container.
现在我们来总结一下 <ng-template> 和 <ng-container> 的区别:
- <ng-template> :使用 * 语法糖的结构指令,最终都会转换为 <ng-template> 或 <template> 模板指令,模板内的内容如果不进行处理,是不会在页面中显示的。
- <ng-container>:是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment 元素,它可用于避免添加额外的元素来使用结构指令。
最后再来看一个 <ng-container> 的使用示例:
模板定义
<div> <ng-container *ngIf="true"> <h2>Title</h2> <div>Content</div> </ng-container> </div>
渲染结果
<div>
<!--bindings={
"ng-reflect-ng-if": "true"
}--><!---->
<h2>Title</h2>
<div>Content</div>
</div>
TemplateRef 与 ViewContainerRef 有什么作用"htmlcode">
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
export interface ViewChildDecorator {
// Type类型:@ViewChild(ChildComponent)
// string类型:@ViewChild('tpl', { read: ViewContainerRef })
(selector: Type<any>|Function|string, {read}?: {read?: any}): any;
new (selector: Type<any>|Function|string,
{read}?: {read?: any}): ViewChild;
}
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]