您好,登錄后才能下訂單哦!
動態創建組件
這篇文章我們將介紹在 Angular 中如何動態創建組件。
定義 AlertComponent 組件
首先,我們需要定義一個組件。
exe-alert.component.ts
import { Component, Input } from '@angular/core'; @Component({ selector: "exe-alert", template: ` <h2>Alert {{type}}</h2> `, }) 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();
每次我們需要創建組件時,我們需要刪除之前的視圖,否則組件容器中會出現多個視圖 (如果允許多個組件的話,就不需要執行清除操作 )。
正如我們之前所說的,resolveComponentFactory()
方法接受一個組件并返回如何創建組件的 ComponentFactory
實例。
在上面代碼中,我們調用容器的 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: ` <h2 (click)="output.next(type)">Alert {{type}}</h2> `, }) 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> <h3>Div one</h3> </div> <div> <h3>Div two</h3> </div> </section>
上面示例中,我們在 section 標簽上應用了 ngIf 指令,從而實現 section 標簽內容的動態顯示。這種方式有個問題是,我們必須添加額外的 DOM 元素。要解決該問題,我們可以使用 <ng-template> 的標準語法 (非*ngIf語法糖):
<ng-template [ngIf]="show"> <div> <h3>Div one</h3> </div> <div> <h3>Div two</h3> </div> </ng-template>
問題是解決了但我們不再使用 *
語法糖語法,這樣會導致我們代碼的不統一。雖然解決了問題,但又帶來了新問題。那我們還有其它的方案么?答案是有的,我們可以使用 ng-container
指令。
<ng-container>
<ng-container>
是一個邏輯容器,可用于對節點進行分組,但不作為 DOM 樹中的節點,它將被渲染為 HTML中的 comment
元素。使用 <ng-container>
的示例如下:
<ng-container *ngIf="show"> <div> <h3>Div one</h3> </div> <div> <h3>Div two</h3> </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-container>
的使用示例:
模板定義
<div> <ng-container *ngIf="true"> <h3>Title</h3> <div>Content</div> </ng-container> </div>
渲染結果
<div> <!--bindings={ "ng-reflect-ng-if": "true" }--><!----> <h3>Title</h3> <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; }
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。