源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

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

  • 时间:2022-09-09 11:09 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:详解Angular 4.x 动态创建组件
[b]动态创建组件[/b] 这篇文章我们将介绍在 Angular 中如何动态创建组件。 [b]定义 AlertComponent 组件[/b] 首先,我们需要定义一个组件。 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";
}
上面代码中,我们定义了一个简单的 [code]alert [/code]组件,该组件有一个输入属性 [code]type [/code],用于让用户自定义提示的类型。我们的自定义组件最终是一个实际的 DOM 元素,因此如果我们需要在页面中插入该元素,我们就需要考虑在哪里放置该元素。 [b]创建组件容器[/b] 在 Angular 中放置组件的地方称为 [code]container [/code]容器。接下来,我们将在[code] exe-app [/code]组件中创建一个模板元素,此外我们使用模板变量的语法,声明一个模板变量。接下来模板元素 [code]<ng-template>[/code] 将会作为我们的组件容器,具体示例如下: app.component.ts
import { Component } from '@angular/core';

@Component({
 selector: 'exe-app',
 template: `
  <ng-template #alertContainer></ng-template>
 `
})
export class AppComponent { }
友情提示:容器可以是任意的 DOM 元素或组件。 在 AppComponent 组件中,我们可以通过 [code]ViewChild [/code]装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回的组件实例或相应的 DOM 元素,但这个示例中,我们需要获取 [code]ViewContainerRef [/code]实例。 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;
}

[b]动态创建组件[/b] 接下来,在 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;
}

在我们定义[code] createComponent() [/code]方法前,我们需要注入[code] ComponentFactoryResolver [/code]服务对象。该 [code]ComponentFactoryResolver [/code]服务对象中,提供了一个很重要的方法 [code]- resolveComponentFactory() [/code],该方法接收一个组件类作为参数,并返回 [code]ComponentFactory [/code]。 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 实例的[code] create() [/code]方法,来创建组件。介绍完上面的知识,我们来实现 AppComponent 组件的 [code]createComponent() [/code]方法:
createComponent(type) {
  this.container.clear(); 
  const factory: ComponentFactory = 
   this.resolver.resolveComponentFactory(AlertComponent);
  this.componentRef: ComponentRef = this.container.createComponent(factory);
}
接下来我们来分段解释一下上面的代码。
this.container.clear();
每次我们需要创建组件时,我们需要删除之前的视图,否则组件容器中会出现多个视图 (如果允许多个组件的话,就不需要执行清除操作 )。
[u]复制代码[/u] 代码如下:
const factory: ComponentFactory = this.resolver.resolveComponentFactory(AlertComponent);
正如我们之前所说的,[code]resolveComponentFactory() [/code]方法接受一个组件并返回如何创建组件的[code] ComponentFactory [/code]实例。
[u]复制代码[/u] 代码如下:
this.componentRef: ComponentRef = this.container.createComponent(factory);
在上面代码中,我们调用容器的[code] createComponent() [/code]方法,该方法内部将调用[code] ComponentFactory [/code]实例的 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 { }
[b]总结[/b] [list] [*] 获取装载动态组件的容器 [/*] [*] 在组件类的构造函数中,注入 [code]ComponentFactoryResolver[/code] 对象 [/*] [*] 调用 [code]ComponentFactoryResolver[/code] 对象的 [code]resolveComponentFactory()[/code] 方法创建 [code]ComponentFactory[/code] 对象 [/*] [*] 调用组件容器对象的 [code]createComponent()[/code] 方法创建组件并自动添加动态组件到组件容器中 [/*] [*] 基于返回的 [code]ComponentRef[/code] 组件实例,配置组件相关属性 (可选) [/*] [*] 在模块 Metadata 对象的 entryComponents 属性中添加动态组件 [list]
  • declarations - 用于指定属于该模块的指令和管道列表 [/*] [*] entryComponents - 用于指定在模块定义时,需要编译的组件列表。对于列表中声明的每个组件,Angular 将会创建对应的一个 ComponentFactory 对象,并将其存储在 ComponentFactoryResolver 对象中 [/*] [/list]
  • [/list] [b]我有话说[/b] [code]<ng-template> [/code]与 [code]<ng-container> [/code]有什么区别? 通常情况下,当我们使用结构指令时,我们需要添加额外的标签来封装内容,如使用[code] *ngIf [/code]指令:
    <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>
    问题是解决了但我们不再使用 [code]* [/code]语法糖语法,这样会导致我们代码的不统一。虽然解决了问题,但又带来了新问题。那我们还有其它的方案么?答案是有的,我们可以使用 [code]ng-container [/code]指令。 [b]<ng-container>[/b] [code]<ng-container>[/code] 是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 [code]comment [/code]元素。使用[code] <ng-container> [/code]的示例如下:
    <ng-container *ngIf="show">
     <div>
      <h2>Div one</h2>
     </div>
     
     <div>
      <h2>Div two</h2>
     </div>
     </ng-container>
    有时我们需要根据 [code]switch [/code]语句,动态显示文本,这时我们需要添加一个额外的标签如[code] <span>[/code] ,具体示例如下:
    <div [ngSwitch]="value">
     <span *ngSwitchCase="0">Text one</span>
     <span *ngSwitchCase="1">Text two</span>
    </div>
    针对这种情况,理论上我们是不需要添加额外的[code] <span>[/code] 标签,这时我们可以使用[code] ng-container [/code]来解决这个问题:
    <div [ngSwitch]="value">
     <ng-container *ngSwitchCase="0">Text one</ng-container>
     <ng-container *ngSwitchCase="1">Text two</ng-container>
    </div>
    介绍完[code] ng-container [/code]指令,我们来分析一下它跟[code] ng-template [/code]指令有什么区别?我们先看以下示例:
    <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. 即[code] <ng-template> [/code]中的内容不会显示。当在上面的模板中添加[code] ngIf [/code]指令:
    <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. 现在我们来总结一下[code] <ng-template>[/code] 和[code] <ng-container>[/code] 的区别: [list=1] [*]<ng-template> :使用 * 语法糖的结构指令,最终都会转换为 <ng-template> 或 <template> 模板指令,模板内的内容如果不进行处理,是不会在页面中显示的。 [/*] [*]<ng-container>:是一个逻辑容器,可用于对节点进行分组,但不作为 DOM 树中的节点,它将被渲染为 HTML中的 comment 元素,它可用于避免添加额外的元素来使用结构指令。[/*] [/list] 最后再来看一个 [code]<ng-container>[/code] 的使用示例: 模板定义
    <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>
    
    [b]TemplateRef 与 ViewContainerRef 有什么作用?[/b] TemplateRef 用于表示内嵌的 template 模板元素,通过 TemplateRef 实例,我们可以方便创建内嵌视图(Embedded Views),且可以轻松地访问到通过 ElementRef 封装后的 nativeElement。需要注意的是组件视图中的 template 模板元素,经过渲染后会被替换成 comment 元素。 ViewContainerRef 用于表示一个视图容器,可添加一个或多个视图。通 ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。(本示例就是通过 ViewContainerRef 对象提供的 API来动态地创建组件视图)。 [b]ViewChild 装饰器还支持哪些查询条件?[/b] 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;
    }
    
    
    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程素材网。
    • 全部评论(0)
    联系客服
    客服电话:
    400-000-3129
    微信版

    扫一扫进微信版
    返回顶部