Angular 7 - Cải thiện hiệu năng với trackBy

Đặt vấn đề

Là angular developer, chúng ta thường xuyên call GET HTTP request để thực hiện việc lấy data rồi sau đó bind data đó lên HTML bằng cách sử dụng *ngFor directive.

*ngFor cung cấp các biến cục bộ, có thể tạo một alias cho các biến đó như:

  • index: number: Chỉ số của phần tử hiện tại trong vòng lặp.
  • first: boolean: True khi phần tử là phần tử đầu tiên của vòng lặp.
  • last: boolean: True khi phần tử là phần tử cuối cùng của vòng lặp.
  • even: boolean: True khi phần tử có chỉ số chẵn vòng lặp.
  • odd: boolean: True khi phần tử có chỉ số lẻ vòng lặp.

*ngFor khi thực hiện lặp qua một collection sẽ có thể tạo một template cho mỗi item.

Sử dụng *ngFor không có trackBy

Khi thực hiện truyền mảng objects hay elements vào *ngFor thì directive sẽ thực hiện vẽ các elements đó lên DOM. Nhưng có một vấn đề xảy ra là khi chúng ta thực hiện thay đổi, cập nhật lại các phần tử trong mảng đó thì tất cả các elements sẽ được tạo lại template và điều đó làm ảnh hưởng không tốt đến hiệu năng của trang web. Và đó là lý do mà TrackByFunction<T> được ra đời.

Hiểu về cơ chế hoạt động của *ngFor

Mặc định khi chúng ta sử dụng *ngFor là không đi kèm trackBy.
*ngFor sẽ track tất cả mảng các đối tượng xem có thay đổi về định danh của đối tượng hay không. Cho nên, nếu có tham chiếu mới của mảng được truyền vào directive và nếu các mảng đó là cùng giá trị thì Angular sẽ không detect được các phần tử đó đã được tạo template trên DOM hiện tại. Đòng nghĩa các phần tử cũ sẽ được xoá bỏ và tạo template mới cho các collection mới có cùng giá trị ban đầu.

Và câu hỏi đặt ra là tại sao Angular team không sử dụng TrackByFunction<T> là cơ chế mặc định?

*ngFor đã trải qua rất nhiều cải tiến của nhóm Angular. Và việc sử dụng trackBy không phải là bắt buộc mà còn phụ thuộc vào tình huống cần xử lý.

Khi nào nên sử dụng trackBy

Chúng ta nên sử dụng trackBy trong các trường hợp:

  • Trong trường hợp template có danh sách lớn hoặc danh sách chiếm phần lớn màn hình, chúng ta thậm chí có thể gặp các vấn đề tối ưu hóa đó và vẫn nhận thấy rằng UI bị chậm do số lượng lớn các phần tử DOM được tạo và hủy.

Cách dùng

Để tránh ảnh hưởng đến hiệu năng web, Angular cho phép chúng ta customize thuật toán tracking mặc định bằng việc cung cấp option trackBy cho ngForOf. Trackby là hàm nhận vào 2 đối số là index và item. Angular sẽ track thay đổi dựa vào giá trị trả về của hàm đó.

import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular';

  users = [];

  constructor() {
    this.users = [
      {id: 1, name: 'HTM1'},
      {id: 2, name: 'HTM2'},
      {id: 3, name: 'HTM3'}
    ];
  }
  
  getUsers() {
    this.users = this.fetchUsers();
  }
  
  fetchUsers() {
    return [
      {id: 1, name: 'HTM1'},
      {id: 2, name: 'HTM2'},
      {id: 3, name: 'HTM3'},
      {id: 4, name: 'HTM4'},
      {id: 5, name: 'HTM5'},
    ];
  }
  
  trackByFn(index, item) {
    return index;
  }
}

<div>
   <div *ngFor="let item of users;trackBy: trackByFn">{{item.name}}</div>
</div>
<button (click)="getUsers()">Get users</button>

Các bạn chạy thử đoạn code và nhìn vào element trên dev tool thì sẽ thấy sự khác biệt của việc render template trong trường hợp sử dụng trackBy và không dùng trackBy.

Code Demo

Tham khảo:

Angular ngForOf