Lập trình Angular - Bài 4.3: Structural Directive - Improve ngFor performance with Track By

Lập trình Angular - Bài 4.3: Structural Directive - Improve ngFor performance with Track By

 

Cải thiện hiệu khi dùng ngFor với trackBy function

Tùy chọn Angular trackBy option giúp cải thiện hiện suất của ngFor nếu danh sách cần hiển thị có nhiều item và liên tục thay đổi.

Trackby trong ngFor

Chúng ta sử dụng ngFor để hiển thị các thành phần có thể lặp lại như mảng trong định dạng danh sách hoặc bảng.

<ol>
  <li *ngFor="let movie of movies;">
    {{movie.title}} - {{movie.releaseDate}}
  </li>
</ol>

Angular tạo 1 phần tử li cho mỗi movie. Vì vậy, nếu có n phần tử, Angular sẽ chèn n node li vào DOM.

Định danh của các phần tử trong trình lặp có thể thay đổi nhưng dữ liệu thì không. Điều này xảy ra khi user thêm, xóa movie, sắp xếp lại danh sách movie theo một thứ tự khác, hoặc đơn giản là làm mới (refresh) lại movie từ backend. Điều này sẽ buộc Angular phải render lại template. Ngay cả khi dữ liệu không thay đổi, phản hồi thứ hai tạo ra các đối tượng có danh tính khác nhau và  Angular phải phá bỏ toàn bộ DOM và xây dựng lại nó (như thể tất cả các phần tử cũ đã bị xóa và tất cả các phần tử mới được chèn vào).

Cách dễ nhất để đạt được điều đó là xóa toàn bộ danh sách và hiển thị lại DOM. Nhưng điều này không hiệu quả và nếu danh sách lớn thì đó là một quá trình rất tốn kém.

Để tránh điều đó, Angular sử dụng object identity để theo dõi các phần tử trong danh sách với các node DOM. Do đó, khi bạn thêm hoặc xóa một item, Angular sẽ theo dõi nó và chỉ cập nhật các item đã sửa đổi trong DOM.

Nhưng nếu bạn làm mới (refresh) lại toàn bộ danh sách từ backend, nó sẽ thay thế các đối tượng trong danh sách movies bằng các đối tượng mới. Ngay cả khi các movie giống nhau,  Angular cũng không thể phát hiện khi các tham chiếu đối tượng đã thay đổi. Do đó, nó coi chúng là mới và hiển thị lại chúng sau khi phá hủy (destroy) những cái cũ.

Ví dụ sau đây cho thấy điều gì sẽ xảy ra khi chúng ta làm mới toàn bộ danh sách. Ứng dụng sẽ hiển thị danh sách các bộ phim. Nó có tùy chọn để thêm, xóa phim một phim và làm mới toàn bộ phim.

  • app.component.ts

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

const EMPTY_STRING = '';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})

export class AppComponent {
  titlestring = "NgForOf with trackBy function";
  moviesMovie[] = [];
  mTitle:string = EMPTY_STRING;
  mDirector:string = EMPTY_STRING;

  constructor() {
  }

  ngOnInit() {
    this.refresh();
  }
 
  remove(i) {
    this.movies.splice(i,1);
  }
 
  addMovie() {
    this.movies.push(new Movie({ title: this.mTitledirector: this.mDirector}))
    this.mTitle = EMPTY_STRING
    this.mDirector = EMPTY_STRING
  }

  refresh() {
    console.log("refresh")
    this.movies = [
      {
        title: 'Zootopia',
        director: 'Byron Howard, Rich Moore',
        cast: 'Idris Elba, Ginnifer Goodwin, Jason Bateman',
        releaseDate: 'March 4, 2016'
      },
      {
        title: 'Batman v Superman: Dawn of Justice',
        director: 'Zack Snyder',
        cast: 'Ben Affleck, Henry Cavill, Amy Adams',
        releaseDate: 'March 25, 2016'
      },
      {
        title: 'Captain American: Civil War',
        director: 'Anthony Russo, Joe Russo',
        cast: 'Scarlett Johansson, Elizabeth Olsen, Chris Evans',
        releaseDate: 'May 6, 2016'
      },
      {
        title: 'X-Men: Apocalypse',
        director: 'Bryan Singer',
        cast: 'Jennifer Lawrence, Olivia Munn, Oscar Isaac',
        releaseDate: 'May 27, 2016'
      },
      {
        title: 'Warcraft',
        director: 'Duncan Jones',
        cast: 'Travis Fimmel, Robert Kazinsky, Ben Foster',
        releaseDate: 'June 10, 2016'
      }
    ];
  }
}

class Movie {
  titlestring;
  directorstring;
  caststring;
  releaseDatestring;

  constructor({ titledirector}) {
    this.title = title;
    this.director = director;
    this.cast = EMPTY_STRING;
    this.releaseDate = (new Date()).toTimeString();
  }
}

  • app.component.html

<h1> {{title}} </h1>
 
<ul>
  <li *ngFor="let movie of movieslet i=index;">
    {{i}}. {{ movie.title }} - {{movie.director}} <button (click)="remove(i)">remove</button>
  </li>
</ul>
 
<button (click)="refresh()">Refresh</button> <br>
 
Title     : <input type="text" [(ngModel)]="mTitle"> 
Director  : <input type="text" [(ngModel)]="mDirector"> 
<button  (click)="addMovie()">Add</button>


Bạn có thể thấy từ ví dụ trên, Angular render toàn bộ DOM mỗi khi chúng ta nhấp vào nút Refresh.

Trackby

Chúng tôi có thể giải quyết vấn đề này bằng cách cung cấp một function cho tùy chọn trackBy trả về một id duy nhất cho mỗi item. ngFor sẽ sử dụng id duy nhất được trả về bởi hàm trackBy để theo dõi các item. Do đó, ngay cả khi chúng tôi làm mới dữ liệu từ backend, id duy nhất sẽ vẫn giữ nguyên và danh sách sẽ không được render lại.

trackBy nhận một hàm có hai đối số: indexitem hiện tại. Nó phải trả về một id nhận dạng duy nhất (uniquely identifies). Ví dụ sau trả về title như  id duy nhất (unique id).

trackByFn(indexitem) {
    return item.title;
}

Trong template, gán function trackByFn mới tạo cho option trackBy in ngFor:

<li *ngFor="let movie of movieslet i=indextrackBytrackByFn">



Trackby multiple fields

trackByFnMultipleFields(indexitem) {
    return item.title + item.director;
}

Dùng hàm trackByFnMultipleFields cho option trackBy:

<li *ngFor="let movie of movieslet i=indextrackBytrackByFnMultipleFields;">

Code Example

Reference

Read More

Đăng nhận xét

Mới hơn Cũ hơn