閱讀有關 Angular 中有 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 元素。
以上範例的寫法其實是 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 會產出的區域變數,
<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 所遍歷目標的內容的時候, Angular 會以以下機制來處理 DOM 的內容
在許多產品中,很常會有當使用者送出某個更新後的內容後,再從 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 都被重新渲染,只有那些跟原本陣列元素內容不同的部分會被重新渲染,是不是很讚呢~~
來做個總結吧