Angular 17+ 高級教程 – Component 組件 の Control Flow

 

前言

Control Flow 是 Angular v17 版本後推出的新模板語法,用來取代 NgIf、NgForOf、NgSwitch 這 3 個 Structure Directive。

Structure Directive 的好處是比較靈活,原理簡單,但是即便用了微語法,它看上去還是相當繁瑣,而且不夠優雅。

Conrol Flow 的好處是它的語法夠美,缺點是不必 Structure Directive 靈活,開發者無法做任何 customize,只能看 Angular 給什麼用什麼。

 

參考

Docs – Built-in control flow

Docs – Deferrable Views

 

@if @else if @else

這個是 NgIf 指令的寫法

<ng-template #loadingTemplate>
  <p>loading...</p>
</ng-template>

<p *ngIf="person$ | async as person; else loadingTemplate">{{ person.name }}</p>

這個是 Control Flow 的寫法

@if(person$ | async; as person) {
  <p>{{ person.name }}</p>
}
@else {
  <p>loading...</p>
}

Control Flow 還支持 @else if,這個是 NgIf 指令不支持的。

@if (value >= 5) {
  <p>greater than or equal to five</p>
}
@else if (value >= 2) {
  <p>greater than or equal to two</p>
}
@else {
  <p>less than two</p>
}

Control Flow @if 的原理

Contorl Flow 不依賴 NgIf 指令,甚至不依賴 ViewContainerRef,它使用的是比較底層的接口,

比如 createAndRenderEmbeddedLView 函數和 addLViewToLContainer 函數。這兩個函數的源碼我們在 Dynamic Component 文章中研究過,

它們也是 ViewContainerRef 內部使用的。

app.component.js

在 renderView 階段,@if、@else if、@else 分別會創建三個 ng-template。

在 refreshView 階段,會執行 ɵɵconditional 函數。第二個參數依據判斷條件它會宣傳第幾個 ng-template。

ɵɵconditional 函數的源碼在 control_flow.ts

 

@for @empty

這個是 NgForOf 指令的寫法

<ng-template #noDataTemplate>
  <p>no datas</p>
</ng-template>

<ng-container *ngIf="people.length > 0 else noDataTemplate">
  <ng-container *ngFor="let person of people; trackBy: trackByName; let index = index">
    <p>index: {{ index }}</p>
    <p>name : {{ person.name }}</p>
  </ng-container>
</ng-container>

這個是 Control Flow 的寫法

@for (person of people; track person.name; let index = $index) {
  <p>index: {{ index }}</p>
  <p>name : {{ person.name }}</p>
} 
@empty {
  <p>no datas</p>
}

有 2 個點非常強

  1. trackByFunction 不需要在 AppComponent 裏定義了
  2. @empty 取代了 NgIf 指令

@for 的原理我們就不翻源碼了,它和 @if 一樣都不依賴 Structure Directive,裏面都是調用底層的接口,也因爲這樣 @for 比 NgForOf 指令速度快很多哦。

 

@switch @case @default

這個是 NgSwitch 指令的寫法

<ng-container [ngSwitch]="status">

  <ng-template [ngSwitchCase]="'Completed'">
    <p>complete</p>
  </ng-template>

  <ng-template [ngSwitchCase]="'Pending'">
    <p>pending</p>
  </ng-template>
  
  <ng-template ngSwitchDefault>
    <p>none</p>
  </ng-template>

</ng-container>

這個是 Control Flow 的寫法

@switch (status) 
{
  @case ('Completed') {
    <p>complete</p>
  }
  @case ('Pending') {
    <p>pending</p>
  }
  @default {
    <p>none</p>
  }
}

提醒:@switch 和 ngSwtich 指令一樣,都沒有 fallthrough 的概念,match 到後會自動 break。

 

@defer @placeholder @loading @error

@defer 是 Control Flow 獨有的,不存在 Defer 指令。

@defer 的作用是延遲顯示一段內容,並且內容裏包含的組件/指令/Pipe 還會延遲加載。

@defer (on timer(5s)) {
  <app-say-hi />
  <app-hello-world />
}

上面這段表示 5 秒中後才輸出 SayHi 和 HelloWorld 組件。

我們可以這樣去理解,@defer 會被 compile 成 ng-template,5 秒鐘後會 createEmbeddedView 然後 insert。

此外 compile 時它還會找出涉及的組件,改成使用 import('/path/component.ts') 的方式動態加載,這樣可以減少 first load 的 bundle size。

app.component.js

@loading,@placeholder,@error

@defer (on timer(2s)) {
  <app-say-hi />
  <app-hello-world />
}
@placeholder {
  <p>blank...</p>
}
@loading {
  <p>loading...</p>
}
@error {
  <p>something wrong</p>
}

@defer 的顯示流程是這樣:

一開始先顯示 @placeholder 的內容,等 on timer 觸發後,顯示 @loading 內容,這時會去 lazyload -> createEmbededView -> insert,最終顯示 @defer 內容。

如果 lazyload 的過程失敗,那就顯示 @error 內容。

minimum 和 after options

有時候 lazyload 或許會太快,loading 一閃而過體驗不好,這時可以設置 after 和 minimum。

@loading(after 100ms; minimum 1s) {
  <p>loading...</p>
}

當開始 lazyload 後,Angular 會開始計時,超過 100ms 後,如果 lazyload 還沒有結束,那就會顯示 @loading 內容,

如果 lazyload 在 1 秒鐘內完成,它不會馬上輸出 @defer 內容,而是會等到 minimum 1s 後才輸出。

注:after 和 minimum 是同時開始計時的。

@placeholder 也支持 minimum options,不過不支持 after 哦。

Trigger

除了 on timer 以外,還有另外 5 個 Triggers。

  1. @defer (on idle)
    idle 指的是遊覽器的 requestIdleCallback 事件
  2. @defer (on immediate)
    immediate 表示一旦 Angular first render 結束後立馬就觸發。
  3. @defer (on timer(500ms))
    timer 就是 setTimeout,可以寫 ms (millisecond) 或者 s (second)
  4. @defer (on hover)
    <div #hoverArea>hover area</div>
    @defer (on hover(hoverArea)) {
      <app-say-hi />
      <app-hello-world />
    }
    
    @defer(on hover) {
      <app-say-hi />
      <app-hello-world />
    }
    @placeholder {
      <div>hover area</div>
    }

    hover element 默認是 @placeholder 內容,我們可以傳入 element 作爲指定的 hover area。

  5. @defer(on viewport)
    viewport 指的是當指定的 element 出現在 viewport 裏,它是通過 IntersectionObserver 實現的。
    和 on hover 一樣,默認的 element 是 @placeholder,我們可以通過傳入參數指定 element。
  6. @defer (on interaction)
    interaction 指的是 click 和 keydown 事件,和 on hover / on viewport 一樣可以指定 element。

我們可以寫 multiple trigger,它的關係是 "or",也就是說只要其中一個 trigger 觸發就執行。

@defer (on viewport; on timer(5s)) {
  <app-say-hi />
  <app-hello-world />
}

當 on viewport 或者超過 5 秒後顯示。

Prefetching

prefetching 的作用是多一個 trigger 用於是否提早 lazyload。

@defer (on viewport; prefetch on timer(5s)) {
  <app-say-hi />
  <app-hello-world />
}
@placeholder {
  <p>placeholder</p>
}

5 秒後,雖然 element 沒有 on viewport 但會先去加載,只加載不顯示,一直到 element on viewport 才顯示。

prefetch 也可以寫 multiple trigger,只要其中一個觸發就執行

@defer (on viewport; on timer(5s); prefetch on timer(3s); prefetch on hover) {
  <app-say-hi />
  <app-hello-world />
}
@placeholder {
  <p>placeholder</p>
}

 

總結

Control Flow 代碼比起 Structure Directive + 微語法整齊很多,代價是無法擴展。

NgIf、NgForOf、NgSwtich 指令相信在不久的將來會被淘汰,但是 ng-template、Structure Directive 和微語法是不會被淘汰的,

而要掌握好 Structure Directive 和微語法,我個人覺得 NgIf、NgForOf、NgSwtich 指令是非常值得參考的。

 

目錄

上一篇 Angular 17+ 高級教程 – Component 組件 の Structural Directive (結構型指令) & Syntax Reference (微語法)

下一篇 TODO

想查看目錄,請移步 Angular 17+ 高級教程 – 目錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章