選單
GSS 技術部落格
在這個園地裡我們將從技術、專案管理、客戶對談面和大家分享我們多年的經驗,希望大家不管是喜歡或是有意見,都可以回饋給我們,讓我們有機會和大家對話並一起成長!
若有任何問題請來信:gss_crm@gss.com.tw
7 分鐘閱讀時間 (1397 個字)

Angular # 23 - 深入 Components [6]

shutterstock_198004562
  • 動態渲染 Component,始之呼吸

  • Angular 需要什麼樣的要素才能透過其底層 API 建立動態 Component 呢?

    • 總共有個要素,要渲染什麼(What),渲染在哪(Where),從哪取得 Component(Where 2.0):

      • ViewContainerRef
        • 這是一個很神奇的東西,它能讓 Angular 理解到這是一個參考點
        • 承上,意思就是告訴 Angular 要渲染在哪(Where)
      • ViewChild
        • 這個東西可以讓我們在 controller 中參考到一個 component 的實例,而且這個實例的型別為 ViewContainerRef
        • 此外,也提供了我們一些 API 用以渲染 component
        • 這個是 What
      • ComponentFactoryResolver
        • 它是一個 Angular 原生的 service
        • 可以替我們取得被宣告在 entryComponents 陣列中的 Component 的 Factory
        • 我想這個就是 Where 2.0
    • 知道了始之呼吸三要素之後,我們來建一個 Alert Component 吧!

      • 這個 Component 在資料有異動時會自動出現,並在一段時間後消失

      • 跟前一篇不同的地方是這次不再有 ng-bootstrap 的幫忙,所以能幫助我們更深入了解 => 如何動態建立並顯示 Component

      • 首先自然是要建立 AlertComponent

          ng g c alert
      • 接著定義它的 Template

          <div class="container mt-2">
            <div class="alert alert-warning" role="alert">
              The data was refreshed at {{ date | date: "medium" }}
            </div>
          </div>
        • 這個 Template 的內容就是顯示資料被更新的時間
        • 時間的資料從 date 這個 property 取得,並透過 DatePipe 處理格式
      • 接著定義它的 Controller

          import { Component, Input } from '@angular/core';
        
          @Component({
            selector: 'app-alert',
            templateUrl: './alert.component.html',
            styleUrls: ['./alert.component.css']
          })
          export class AlertComponent {
        
            constructor() { }
        
            @Input() date: Date;
        
          }
        • 很單純的 Controller,只有一個 @Input,就是 date,且型別為 Date
        • 從這我們可以了解動態建立的 Component 真正複雜的不是它顯示的內容,而是顯示它的機制
      • 如同 NodeDetailsComponent,我們要在 app.module.ts 告訴它要幫我們備好一個 Factory class,因此要將 AlertComponent 加到 entryComponents 陣列:

          entryComponents: [
              NodesDetailComponent,
              AlertComponent
          ],
      • DashboardComponent 的 template 依然需要一個 placeholder

          <ng-template #alertBox></ng-template>
        • 跟 ng-bootstrap 不太一樣的是這裡不是用 <template>,而是用到了 <ng-template>,它的官網說明在這裡
        • 簡單說 ng-template 預設不會渲染任何被其包裝的內容,但相對它的使得我們得以控制裡面要顯示什麼內容
        • 這邊還有一個 local template variable,忘記是什麼的回去複習一下,總之要記得有這個 #alertBox 的存在
      • 再來就是要填入 AppComponent 的 Controller 了

          import { Component, ViewChild, ViewContainerRef, ComponentRef, ComponentFactoryResolver } from '@angular/core';
          import { DashboardComponent } from './dashboard/dashboard.component';
          import { AlertComponent } from './alert/alert.component';
        
          @Component({
            selector: 'app-root',
            templateUrl: './app.component.html',
            styleUrls: ['./app.component.css']
          })
          export class AppComponent {
            alertRef: ComponentRef<AlertComponent>;
            @ViewChild(DashboardComponent) dashboard: DashboardComponent;
            @ViewChild('alertBox', { read: ViewContainerRef }) alertBox: ViewContainerRef;
        
            constructor(private ComponentFactoryResolver: ComponentFactoryResolver) { }
        
            alert(date) {
              if (!this.alertRef) {
                const alertComponent = this.ComponentFactoryResolver.resolveComponentFactory(AlertComponent);
                this.alertRef = this.alertBox.createComponent(alertComponent);
              }
              this.alertRef.instance.date = date;
              this.alertRef.changeDetectorRef.detectChanges();
              setTimeout(() => this.destroyAlert(), 5000);
            }
        
            destroyAlert() {
              if (this.alertRef) {
                this.alertRef.destroy();
                delete this.alertRef;
              }
            }
        
            refresh() {
              this.dashboard.generateData();
            }
          }
        • import 了 ViewChild, ViewContainerRef, ComponentRef, ComponentFactoryResolver,想必是要大開殺戒了(X)
        • 首先看到 alertRef 這個屬性,它會參考到 AlertComponent,特別留意它的型別是 ComponentRef<AlertComponent>,目前只有參考,還沒有建立 instance,不過之後可以對實例作操作
        • 再來是 DashboardComponentViewChild,這邊還沒什麼特別的,可是 alertBox 的 ViewChild 有很明顯的不同了:
          • 它的第一個參數是字串,放的是 local template variable 的名稱
          • 第二個參數是一個物件,表明了要 read(讀入) 一個 ViewContainerRef,其實就是透過 #alertBox 將取得的物件轉型成 ViewContainerRef(還記得上面說這個可以幹嘛嗎?)
        • 目前為止都只有參考,還沒有建立任何實例,實例是會透過注入的 service => ComponentFactoryResolver 來產生的
        • 主要的魔法是發生在 alert 這個方法:
          • 它會先檢查 alertRef 有沒有參考到什麼實例了,如果沒有的話就透過 ComponentFactoryResolver 建一個 Factory
          • 接著 alertRef 參考到的實例會透過呼叫 alertBox(目前被轉型成 ViewContainerRef) 的 API createComponent注入(透過前面的 Factory) 一個 AlertComponent 的實例
        • 統整一下,呼叫 alert 之後
          • alertBox 會是真正的實例
          • alertRef 會是指向 alertBox 的參考
          • 真正的實例是透過 ComponentFactoryResolver 這個 service 所建立的 Factory,在 alertBox 呼叫 createComponent 時注入的
          • 之後的操作都是透過 alertRef
        • 魔法尚未結束,透過 alertRef,我們設定了 AlertComponent 的 @Input,並同時觸發 Change Detection。之所以手動觸發是因為我們是手動設定 Component 的值的,所以必須通知 Angular 有這件事,它才會 Render 出我們要的 AlertComponent 在畫面上
        • 還沒呢…啊魔法是誰施展的? 當然是 DashboardComponent,因為資料是它在更新的,所以事件由它發出
      • 那麼就來調整一下 DashboardComponent 的 controller 如下:

          // ...(略)
          @Output() onRefresh: EventEmitter<Date> = new EventEmitter<Date>();
        
          //...(略)
          generateData() {
             //...(略)
             this.onRefresh.emit(new Date());
          }
        • 因為 controller 實在是太長了,所以看重點就好,其餘的都照舊
        • 首先我們加了一個 @Output 用來發出事件,同時留意這次的 EventEmitter 的型別參數為 Date,代表在發出事件時會傳入一個 Date 型別的資料,供監聽者挪用
        • 產資料的時候會呼叫 emit,同時傳入現在的時間(new Date())
      • 最後自然是在發出事件的時候顯示 AlertComponent 了

          <app-dashboard (onRefresh)="alert($event)"></app-dashboard>
        • 別忘了這個是 AppComponent 的 template
        • 有沒有看到那個 $event,那是 Angular 特有的語法,可以取得伴隨著事件傳來的參數
  • 小結

    • 這兩篇我們學了很多東西的綜合使用
      • LifeCycle Hook
      • Change Detection
      • Dynamic Component Rendering
    • 內容都挺深奧的,但實際上是很常用到的東西
      • 習慣成自然,多練習就會明白 Angular 為何這樣設計
      • 記得幾個關鍵的 service,上官網可以看到更詳細的解說
Angular #24 - 深入 Service [1]
Angular #22 - 深入 Components [5]

相關文章

 

評論

尚無評論
已經注冊了? 這裡登入
Guest
2025/05/22, 週四

Captcha 圖像