欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Angular 4.x 动态创建组件

程序员文章站 2022-05-26 09:33:27
动态创建组件 这篇文章我们将介绍在 angular 中如何动态创建组件。 定义 alertcomponent 组件 首先,我们需要定义一个组件。 exe-a...

动态创建组件

这篇文章我们将介绍在 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?: any[][], rootselectorornode?: string|any,
   ngmodule?: ngmoduleref<any>): componentref<c>;
}

通过观察 componentfactory 抽象类,我们知道可以通过调用 componentfactory 实例的 create() 方法,来创建组件。介绍完上面的知识,我们来实现 appcomponent 组件的 createcomponent() 方法:

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> 的区别:

  1. <ng-template> :使用 * 语法糖的结构指令,最终都会转换为 <ng-template> 或 <template> 模板指令,模板内的内容如果不进行处理,是不会在页面中显示的。
  2. <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 有什么作用?

templateref

用于表示内嵌的 template 模板元素,通过 templateref 实例,我们可以方便创建内嵌视图(embedded views),且可以轻松地访问到通过 elementref 封装后的 nativeelement。需要注意的是组件视图中的 template 模板元素,经过渲染后会被替换成 comment 元素。

viewcontainerref

用于表示一个视图容器,可添加一个或多个视图。通 viewcontainerref 实例,我们可以基于 templateref 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,viewcontainerref 的主要作用是创建和管理内嵌视图或组件视图。(本示例就是通过 viewcontainerref 对象提供的 api来动态地创建组件视图)。

viewchild 装饰器还支持哪些查询条件?

viewchild 装饰器用于获取模板视图中的元素,它支持 type 类型或 string 类型的选择器,同时支持设置 read 查询条件,以获取不同类型的实例。

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;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。