昨天我們介紹了使用 *ngComponentOutlet
的方法來動態產生元件,其實它是一個使用 ViewContainerRef
來顯示不同內容的行為,因此我們也可以不透過樣板語法的方式,改成自行在程式中產生元件實體後方到畫面上;今天我們就稍微深入的來看看比較複雜的方式來動態載入元件吧!
類型:技巧
難度:5 顆星
實用度:4 顆星
要動態載入一個元件,有以下幾個步驟
首先,我們先建立一個 directive,如下:
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[appDynamicComponentHost]'
})
export class DynamicComponentHostDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
在上面的程式中,我們注入了 ViewContainerRef
,代表的是宿主元素的樣板實體,當我們能拿到樣板實體時,就能夠在這個實體上,放置不同的元件。
接著我們會在樣板中使用這個 directive,來決定哪個宿主元素是用來載入不同元件的,所以原本一堆 *ngIf
的 HTML 變成了一行!
<ng-container appDynamicComponentHost></ng-container>
接著,我們要在程式中取得這個 directive,同時也代表能夠取得對應的 ViewContainerRef
,我們才能夠改變宿主元素的內容,要取得 directive 的方式很簡單,用 @ViewChild
就好了。
@ViewChild(DynamicComponentHostDirective) dynamicComponentLoader: DynamicComponentHostDirective;
接著,我們要使用 Angular 提供的 ComponentFactoryResolver
服務來產生一個元件,整個程式碼大致如下:
export class AppComponent implements OnInit {
@ViewChild(DynamicComponentHostDirective) dynamicComponentLoader: DynamicComponentHostDirective;
private _chooseForm = 'A';
get chooseForm() {
return this._chooseForm;
}
set chooseForm(value) {
this._chooseForm = value;
this.setDynamicComponent();
}
mapping = new Map<string, any>(
[
['A', ComponentAComponent],
['B', ComponentBComponent],
['C', ComponentCComponent],
]
);
constructor(private componenFactoryResolver: ComponentFactoryResolver) { }
ngOnInit() {
this.setDynamicComponent();
}
setDynamicComponent() {
const targetComponent = this.mapping.get(this.chooseForm);
const componentFactory = this.componenFactoryResolver.resolveComponentFactory(targetComponent);
const viewContainerRef = this.dynamicComponentLoader.viewContainerRef;
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
}
}
看起來很長一串,但主要都是一些對應和注入的程式碼,重點其實在 setDynamicComponent()
裡面,讓我們來看一下裡面的程式碼內容:
const componentFactory = this.componenFactoryResolver.resolveComponentFactory(targetComponent);
使用注入的 ComponentFactoryResolver
來取得要產生目標元件的工廠類別實體。
const viewContainerRef = this.dynamicComponentLoader.viewContainerRef;
viewContainerRef.clear();
取得要動態載入元件的 directive 的 ViewContainerRef
。
const componentRef = viewContainerRef.createComponent(componentFactory);
使用取得的 ViewContainerRef
的 createComponent
方法,來建立元件,建立的依據是剛剛產生的工廠類別實體;建立後會取得一個 ComponentRef
的類別實體,代表的是實際上建立的元件實體參考,因此我們可以透過 componentRef.instance
的方式取得元件實體本身,因此若有需要傳資料給元件,可以使用轉型的方式,例如:
(componentRef.instance as SomeComponent).data = {};
最後當然不要忘記,有動態產生需求的元件,一定要放入對應模組中的 entryComponents: []
設定中,就算大功告成啦!
ViewContainerRef
提供了許多有趣的方法,除了我們剛才用到的 createComponent()
以外,另外還有一個方法 createEmbeddedView()
可以用來產生樣板上 <ng-template>
的內容,所以要動態載入樣板中的一個 template 也是完全沒問題的一件事情!
@ViewChild('customTemplate') customTemplate: TemplateRef;
setDynamicComponent() {
const viewContainerRef = this.dynamicComponentLoader.viewContainerRef;
viewContainerRef.createEmbeddedView(this.customTemplate);
}
這麼一來,不管是 *ngComponentOutlet
還是 *ngTemplateOutlet
,我們都能夠透過自己的方式去顯示啦!
本日的程式碼:
https://stackblitz.com/edit/ironman2019-dynamic-component-loader
大大您好,想請問一下
如果把 ViewContainerRef
注入到某個 Component
中 (ex. AppComponent
)
接著執行 this.viewContainerRef.clear()
這樣不會將 AppComponent
的內容清空,這是正常的嗎 ? (如圖)
url: https://stackblitz.com/edit/angular-viewcontainerref-exercise?file=src%2Fapp%2Fapp.component.ts
clear()
不會將原本的內容清空,而是將動態產生的元件清除喔
原來如此! 感謝
剛去觀察動態元件的 DOM
的位置,是在注入 ViewContainerRef
的元件的 DOM
的後面,而不是該 DOM
的裡面 (如圖)