寫在最前,本文提到的“模板”都是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