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

Wuyang發表於2019-04-28
寫在最前,本文提到的“模板”都是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原始碼,其中ngForngIf皆是由第2種方式來實現得。

參考資料:


相關文章