前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Angular 动态创建组件

Angular 动态创建组件

作者头像
阿宝哥
发布2019-11-05 15:46:40
3.6K0
发布2019-11-05 15:46:40
举报
文章被收录于专栏:全栈修仙之路全栈修仙之路

本文我们将介绍在 Angular 中如何动态创建组件。

定义 AlertComponent 组件

首先,我们先来定义一个 AlertComponent 组件:

代码语言:javascript
复制
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: "alert",
  template: `
    <h1 (click)="output.next('output')">Alert {{ type }}</h1>
  `,
})
export class AlertComponent {
  @Input() type: string = "success";
  @Output() output = new EventEmitter();
}

上面代码中,我们定义了一个简单的 AlertComponent 组件,该组件有一个输入属性 type ,用于让用户自定义提示的类型,此外还包含了一个输出属性 output,用于向外部组件输出信息。我们的自定义组件最终是一个实际的 DOM 元素,因此如果我们需要在页面中插入该元素,我们就需要考虑在哪里放置该元素。

创建组件容器

在 Angular 中放置组件的地方称为容器。接下来,我们将在根组件中创建一个模板元素,另外我们使用模板变量的语法,声明一个模板变量。接下来模板元素 <ng-template> 将会作为我们的组件容器,具体示例如下:

代码语言:javascript
复制
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <ng-template #alertContainer></ng-template>
  `
})
export class AppComponent { }

在 AppComponent 组件中,我们可以通过 ViewChild 装饰器来获取视图中的模板元素,如果没有指定第二个查询参数,则默认返回的 ElementRef 实例,但这个示例中,我们需要获取 ViewContainerRef 实例。

ViewContainerRef 用于表示一个视图容器,可添加一个或多个视图。通过 ViewContainerRef 实例,我们可以基于 TemplateRef 实例创建内嵌视图,并能指定内嵌视图的插入位置,也可以方便对视图容器中已有的视图进行管理。简而言之,ViewContainerRef 的主要作用是创建和管理内嵌视图或组件视图。

根据以上需求,更新后的代码如下:

代码语言:javascript
复制
import { Component, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'my-app',
  template: `
    <ng-template #alertContainer></ng-template>
  `
})
export class AppComponent {
  @ViewChild("alertContainer", { read: ViewContainerRef }) container: ViewContainerRef;
}

动态创建组件

接下来,在 AppComponent 组件中,我们来添加两个按钮,用于创建 AlertComponent 组件。

代码语言:javascript
复制
import { Component, ViewChild, ViewContainerRef } from '@angular/core';

@Component({
  selector: 'my-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 实例 。

这里我们在 AppComponent 组件构造函数中,注入 ComponentFactoryResolver 服务:

代码语言:javascript
复制
constructor(private resolver: ComponentFactoryResolver) {}

接下来我们再来看一下 ComponentFactory 抽象类:

代码语言:javascript
复制
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() 方法:

代码语言:javascript
复制
createComponent(type) {
  this.container.clear();
  const factory: ComponentFactory<AlertComponent> =
    this.resolver.resolveComponentFactory(AlertComponent);
  this.componentRef = this.container.createComponent(factory);
}

接下来我们来分段解释一下上面的代码。

代码语言:javascript
复制
this.container.clear();

每次我们需要创建组件时,我们需要删除之前的视图,否则组件容器中会出现多个视图(如果允许多个组件的话,就不需要执行清除操作)。

代码语言:javascript
复制
const factory: ComponentFactory<AlertComponent> =
   this.resolver.resolveComponentFactory(AlertComponent);

正如我们之前所说的,resolveComponentFactory() 方法接受一个组件并返回 ComponentFactory 实例。

代码语言:javascript
复制
this.componentRef = this.container.createComponent(factory);

在上面代码中,我们调用容器对象的 createComponent() 方法,该方法内部将调用 ComponentFactory 实例的 create() 方法创建对应的组件,并将组件添加到我们的容器中。

现在我们已经获得新组件的引用,即可以我们可以手动设置组件的输入类型:

代码语言:javascript
复制
this.componentRef.instance.type = type;

同样我们也可以订阅组件的输出属性:

代码语言:javascript
复制
this.componentRef.instance.output.subscribe(event => console.log(event));

当我们不需要已创建的组件时,我们也可以通过调用 destroy() 方法销毁组件:

代码语言:javascript
复制
ngOnDestroy() {
  this.componentRef.destroy(); 
}

最后我们需要将动态组件添加到 NgModule 的 entryComponents 属性中:

代码语言:javascript
复制
@NgModule({
  ...,
  declarations: [AppComponent, AlertComponent],
  bootstrap: [AppComponent],
  entryComponents: [AlertComponent],
})
export class AppModule { }

完整示例

  1. alert.component.ts
代码语言:javascript
复制
import { Component, EventEmitter, Input, Output } from '@angular/core';

@Component({
  selector: "alert",
  template: `
    <h1 (click)="output.next('output')">Alert {{ type }}</h1>
  `,
})
export class AlertComponent {
  @Input() type: string = "success";
  @Output() output = new EventEmitter();
}
  1. app.component.ts
代码语言:javascript
复制
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, ComponentFactory, ComponentRef, OnDestroy } from '@angular/core';
import { AlertComponent } from './alert.component';

@Component({
  selector: 'my-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) {
    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()
  }
}
  1. app.module.ts
代码语言:javascript
复制
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppComponent } from './app.component';
import { AlertComponent } from './alert.component';

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, AlertComponent],
  entryComponents: [AlertComponent],
  bootstrap: [AppComponent]
})
export class AppModule { }

下面我们来总结一下动态加载组件的流程:

  • 获取装载动态组件的容器。
  • 在组件类的构造函数中,注入 ComponentFactoryResolver 对象。
  • 调用 ComponentFactoryResolver 对象的 resolveComponentFactory() 方法创建 ComponentFactory 对象。
  • 调用组件容器对象的 createComponent() 方法创建组件并自动添加动态组件到组件容器中。
  • 基于返回的 ComponentRef 组件实例,配置组件相关属性(可选)。
  • 在模块 Metadata 对象的 entryComponents 属性中添加动态组件:
    • declarations —— 用于指定属于该模块的指令和管道列表。
    • entryComponents —— 用于指定在模块定义时,需要编译的组件列表。对于列表中声明的每个组件,Angular 将会创建对应的一个 ComponentFactory 对象,并将其存储在 ComponentFactoryResolver 对象中。

通过 ComponentFactoryResolver 对象,我们实现了动态创建组件的功能。但创建的过程还是有点繁琐,为了提高开发者体验和开发效率,Angular 引入了 ngComponentOutlet 指令。 好的,我们马上来感受一下 ngComponentOutlet 指令带来的便利。

感兴趣的同学也可以在线查看 Stackblitz 完整示例。

ngComponentOutlet

  1. 更新根组件 AppComponent 模板,应用 ngComponentOutlet 指令:
代码语言:javascript
复制
@Component({
  selector: 'my-app',
  template: `
    ...
    <ng-container *ngComponentOutlet="alertComponent"></ng-container>
  `
})
  1. 更新 AppComponent 组件类,新增 alertComponent 属性:
代码语言:javascript
复制
import { AlertComponent } from './alert.component';

export class AppComponent implements OnDestroy {
  alertComponent = AlertComponent;
  //...
}

其实 ngComponentOutlet 指令除了包含 ngComponentOutlet 输入属性之外,还包含 ngComponentOutletInjector、ngComponentOutletContent 和 ngComponentOutletNgModuleFactory 输入属性,想深入了解的同学可以参考一下 Angular 官方的说明文档。

代码语言:javascript
复制
@Component({
  selector: 'my-app',
  template: `
    <ng-template #alertContainer></ng-template>
    <button (click)="createComponent('success')">Create success alert</button>
    <button (click)="createComponent('danger')">Create danger alert</button>
    <div *ngComponentOutlet="alertComponent"></div>
  `
})

参考资源

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018/08/15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 定义 AlertComponent 组件
  • 创建组件容器
  • 动态创建组件
  • 完整示例
  • ngComponentOutlet
  • 参考资源
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档