iT邦幫忙

2021 iThome 鐵人賽

DAY 19
0
Modern Web

新新新手閱讀 Angular 文件30天系列 第 19

新新新手閱讀 Angular 文件 - ngFor(1) - Day19

本文內容

閱讀有關 Angular 中有 ngFor 語法的筆記內容。

ngFor 在幹嘛的?

它用來遍歷某個集合(比如: 某一個陣列)中的每一個元素,並在遍歷的當下為該元素渲染出屬於它的樣板到畫面上。

ngFor用法

[View]

<ul>
  <li *ngFor="let user of userInfo; index as i">{{ i }} - {{ user.name }}</li>
</ul>

[TypeScript]

export class AppComponent {
  userInfo = [{ name: 'Jared' }, { name: 'Tom' }, { name: 'Max' }];
}

上面的範例可以看到我們在 li 元素上加上了 ngFor 的內容,用以遍歷 userInfo 的陣列內容,此時,當該陣列中的元素被遍歷到的時候,就會為該元素產出一個 li 元素,所以, userInfo 是一個有三個元素的陣列,故畫面上會被產出三個 li 元素。

shorthand form *ngFor

以上範例的寫法其實是 ngFor 語法的簡寫,那在官方文件有說明以上範例中的 ngFor 實際上 Angular 會把它翻譯成這樣

<ng-template ngFor let-user [ngForOf]="userInfo" let-i="index">
  <li> {{ user.name }}</li>
</ng-template>

當 Angular 遇到 ngFor 前面的星號 *(asterisk) ,它就會將產出一個 ng-template 將 ngFor 所在的元素包起來(以這邊的範例是 li),接著,其他的屬性對應的對象,我列在下面
ngFor: 對應到原本的 ngFor
[ngForof]="userInfo": 這句話的意思是要被遍歷的對象為 userInfo
let-user: 對應到原本的 *ngFor="let user of userInfo" 中的 user
let-i="index": 對應到原本的 let index as i

ngFor 所產生出的 local variables

在官方文件上還有特別列出了 ngFor 會產出的區域變數,

<li *ngFor="let user of users; index as i; first as isFirst">
  {{i}}/{{users.length}}. {{user}} <span *ngIf="isFirst">default</span>
</li>

index: number: 當下被遍歷到的元素在該所屬陣列中的位置
count: number: 被遍歷對象的元素總數,以上面的範例為例的話,就是 users.length
first: boolean: 當被遍歷的當下元素是該被遍歷對象的第一個元素時,會回傳 true
last: boolean: 當被遍歷的當下元素是該被遍歷對象的最後一個元素時,會回傳 true
even: boolean: 當被遍歷的當下元素是該被遍歷對象的偶數元素時,會回傳 true
odd: boolean: 當被遍歷的當下元素是該被遍歷對象的奇數元素時,會回傳 true

改變 ngFor 新增/刪減遍歷元素的機制

當改變 ngFor 所遍歷目標的內容的時候, Angular 會以以下機制來處理 DOM 的內容

  1. 新增遍歷目標的內容: Angular 將會產出一個新的實例樣板,並將它渲染到畫面上。
  2. 刪除遍歷目標的內容: Angular 將會刪除一個指定的實例,並將其從畫面上拔除。
  3. 當遍歷目標內的元素的位置被調換,則它們在畫面上的 DOM 位置也會被對調到相對應的位置。

ngFor 的 trackBy 屬性,來優化 ngFor 渲染機制

在許多產品中,很常會有當使用者送出某個更新後的內容後,再從 server 取回更新後的內容,接著再將它渲染到畫面上。
這邊做個簡單的範例
[View]

<ul>
  <li *ngFor="let user of userInfo; index as i">{{ i }} - {{ user.name }}</li>
</ul>
<button (click)="getUserInfoArray()">getUserInfo</button>

[TypeScript]

export class AppComponent {
  userInfo = [{ name: 'Jared' }, { name: 'Tom' }, { name: 'Max' }];

  getUserInfoArray() {
    this.userInfo = [
      { name: 'Jared' },
      { name: 'Tom' },
      { name: 'Max' },
      { name: 'Wendy' },
      { name: 'Mars' },
      { name: 'Suns' },
    ];
  }
}

以上範例的操作結果

可以看到以上的影片,當按下按鈕,會模仿重新 GET 後的陣列內容並將其渲染到畫面上。接著,在 DOM 上可以看到 ul 和 li 的 DOM 元素全部都有閃一次,有閃的部分就代表它被重新渲染到畫面上了,由此可知, ul 和 li 全部都被重新渲染了。

但事實上並不是全部的陣列的內容都是新的,只有幾個元素是新增的,這樣的話,連那些重複的元素都被重新渲染的話,豈不很浪費瀏覽器的資源嗎~~
所以,我們可以引用 ngFor 的 trackBy 屬性來優化以上的情境,來改寫一下上面的範例
[View]

<ul>
  <li *ngFor="let user of userInfo; index as i; trackBy: trackByItems">{{ i }} - {{ user.name }}</li>
</ul>
<button (click)="getUserInfoArray()">getUserInfo</button>

[TypeScript]

export class AppComponent {
  userInfo = [{ name: 'Jared' }, { name: 'Tom' }, { name: 'Max' }];
  
  trackByItems(index, item) {
    return item.name;
  }
  
  getUserInfoArray() {
    this.userInfo = [
      { name: 'Jared' },
      { name: 'Tom' },
      { name: 'Max' },
      { name: 'Wendy' },
      { name: 'Mars' },
      { name: 'Suns' },
    ];
  }
}

可以看到我們在 ngFor 的內容中,加入 trackBy: trackByItems 代表它會去追蹤 trackByItems 這個函式回傳的內容。在 trackByItems 函式有兩個參數分別是 index 和 item ,index 就代表該元素在其所屬陣列中的元素位置, item 的話就代表遍歷當下的元素。然後,我們會回傳 item.name 就是代表去鑒察是否該元素的 name 屬性內容是之前陣列沒有的。

以上這個新增的程式碼效果,為若該元素的 name 屬性內容沒有變化的話,該元素的 template 是不會被重新渲染的喔。
讓我們來看一下修改後的操作結果

可以發現並不是所有 ul 裡面的 li 都被重新渲染,只有那些跟原本陣列元素內容不同的部分會被重新渲染,是不是很讚呢~~

Summary

來做個總結吧

  1. 我們可以透過 ngFor 來遍歷某個陣列,而被遍歷元素的同時,會為哀元素產生出屬於它的樣板。
  2. 可以加入 trackBy 屬性來優化 ngFor 渲染機制。

上一篇
新新新手閱讀 Angular 文件 - ngIf - Day18
下一篇
新新新手閱讀 Angular 文件 - ngFor(2) - Day20
系列文
新新新手閱讀 Angular 文件30天30

尚未有邦友留言

立即登入留言