Angular-3種創建動態內容的方式

寫在最前,本文提到的「模板」都是ng-template;假設的模態組件也只是實現模態內容;為了縮減文章的篇幅,只保留了重要的部分。完整的例子在線上。


在開發過程中,難免會遇到公共組件需要Input模板或input組件的時候,以增加公共組件交互或展示的靈活性。

題外話:input組件的方式,可以擴展為依靠服務在業務模塊中進行配置,以達到每個模塊所用的同一個公共組件擁有不同的交互。下一篇文章將會談談。

1. 使用 ngTemplateOutlet 和 ngComponentOutlet

ngTemplateOutlet: 根據一個提前備好的 TemplateRef 插入一個內嵌視圖。ngComponentOutlet: Instantiates a single Component type and inserts its Host View into current View. NgComponentOutlet provides a declarative approach for dynamic component creation.

假設要寫一個表格組件,名為wtable,需要自定義單元格內容,組件ts有如下內容。

線上代碼:

  • 使用場景

<wtable [columns]="[姓名,年齡]" [rows]="[{name: ww, age: 11}, {name:yy, age: 22}]" [cellContent]="custom"></wtable>

<ng-template #custom let-data let-column="column" let-row="row" let-index="index">
<!-- 在這裡就可以獲取數據搞事情啦 -->
{{column}}: {{data}}<br>
{{index}} 行<br>
行數據: {{row | json}}
</ng-template>

  • wtable組件的html

<tbody>
<tr *ngFor="let row of rows;index as i;">
<td *ngFor="let cell of row | keyvalue">
<!-- 模板 -->
<ng-container *ngIf="tpl">
<ng-container *ngTemplateOutlet="tpl; context:outCellContext(cell.key, cell.value, row, i);"></ng-container>
</ng-container>
<!-- 組件不太好用 -->
<ng-container *ngIf="comp">
<ng-container *ngComponentOutlet="comp"></ng-container>
</ng-container>
<td>
</tr>
</tbody>

  • wtable組件ts

// 此處為內部變數傳遞給外部模板使用,所需的數據
outCellContext(cellKey, cellValue, row, index) {
return {
$implicit: cellValue, // 默認傳出cellValue數據
column: cellKey, // 指定欄位
row: row, // 行數據
index: index // 行索引
}
}
}

2. 使用ViewContainerRef

表示可以將一個或多個視圖附著到組件中的容器。

假設要寫一個模態框組件wmodal,需要將模板或組件在模態框里展示。但是又不想html里用*ngIf來判斷內容的顯示,兩個三個還可以接受,那要是7、8個或以上呢?讓我們看看下面這個例子。

線上代碼。

  • 使用場景

<!-- 組件 -->
<wmodal [content]="comp" [compParams]="{data: 我是調用wmodal傳入的值}" (compOut)="wmodalOut($event)"></wmodal>

<!-- 模板 -->
<wmodal [content]="modalTpl"></wmodal>
<ng-template #modalTpl let-data let-other="other">
data: {{data | json}} <br>
other: {{other}}
</ng-template>

  • 在wmodal內需要做點什麼呢?首先是html

// 佔個位,這裡我要放傳入的模板或組件了
<ng-template #container></ng-template>

  • 接著是wmodal的ts

ngAfterContentInit() {
// 依然是判斷content類型
if (this.content instanceof Type) {
const comp = this.container.createComponent(this._compFac.resolveComponentFactory(this.content));
// 將組件所需參數合併到組件實例中
if (this.compParams) {
Object.assign(comp.instance, this.compParams);
}
// 訂閱組件
for (const prop in comp.instance) {
if (comp.instance.hasOwnProperty(prop)) {
const subject = comp.instance[prop];
// 篩選組件output事件
if (subject instanceof EventEmitter) {
this._compSubs.push(
// 訂閱組件output事件
subject.subscribe(data => {
this.compOut.emit(data);
})
);
}
}
}
} else {
// 創建模板就比較簡單了
// 留意一下第二個參數,若是需要將組建的某些數據傳出則可以這樣
const _data = {a: 1, b: 2};
this.container.createEmbeddedView(this.content, {$implicit: _data, other: 2});
}
}

3. 使用ApplicationRef

A reference to an Angular application running on a page.

假設還是modal組件

  • 使用場景

<wmodal2 [content]="comp" [compParams]="{data: 我是調用wmodal2傳入的值}" (compOut)="wmodalOut($event)"></wmodal2>

  • 慣例,wmodal2 html

<!-- 什麼都沒有 -->

  • wmodal2 ts

ngAfterContentInit() {
const comp = this._compFaRes.resolveComponentFactory(this.content).create(this._injector);
this._e.nativeElement.appendChild(comp.location.nativeElement);

// 此處與第2點一樣,訂閱output和給input賦值

// 去掉timeout你就知道為什麼了
// 具體原因可以看我專欄的的第一篇文章
const timeId = setTimeout(() => {
this._appref.attachView(comp.hostView);
clearTimeout(timeId);
}, 100)
}

結束語

本文並未對3種方式進行對比,只是個人對於3種方式的使用理解。 簡單的總結一下:

  • 使用1,會讓html代碼比較多,不易維護;而且1是通過2來實現得,傳送門;
  • 1、2都需要插座代碼(可見的outlet),對於模板的創建都需要context來傳遞變數,3不需要插座代碼;
  • 2是比較常見的使用方式
  • 使用3的方式來僅可以創建組件且還有更大的用處
  • 創建組件都需要手動為input賦值並訂閱output

有興趣的還可以看看angular源碼,其中ngFor和ngIf皆是由第2種方式來實現得。

參考資料:

  • angular.cn
  • 深入Angular:理解Component動態載入
  • Angular 4.x 修仙之路: Angular 2 TemplateRef & ViewContainerRef

推薦閱讀:

TAG:Angular | 前端開發 | 前端框架 |