開発覚書はてな版

個人的な開発関連の備忘録

【Angular】ngFor trackBy の実装

概要

Angular の ngForにはtrackByという設定項目があります。

ngForはtrackByを使用しない場合、コレクションに変更があったら全てのDOMを再生成します。 trackByを使用することで変更箇所のDOMのみ再生成するように設定できます。

再描画が多いngForを使用している箇所ではパフォーマンスチューニングの一環としてtrackByの使用をおすすめします。

実行環境

  • Node.js 10.9.x

使用ライブラリ

  • Angular 7.1.x

実装時の注意点

  • trackByの戻り値は一意な値を戻してください。

サンプルソース

list.component.ts
import { Component, Input, OnInit, OnDestroy } from '@angular/core';

@Component({
  selector: 'custom-list',
  template: `<li>{{num}}</li>`
})
export class ListComponent implements OnInit, OnDestroy {

  @Input() num = 0;

  @Input() title = 'list';

  ngOnInit(): void {
    console.log(`${this.title}: init - ${this.num}`);
  }

  ngOnDestroy(): void {
    console.log(`${this.title}: destroy - ${this.num}`);
  }
}
app.component.html
<div style="margin-top: 10px;">
  <div>
    <h4>
      Non trackBy
    </h4>
    <div>
      <button (click)="setItem1()">Apply</button>
    </div>
    <div>
      <ul>
        <ng-container *ngFor="let item of item1">
          <custom-list [num]="item.id" title="non-trackBy"></custom-list>
        </ng-container>
      </ul>
    </div>
  </div>
  <div style="margin-top: 10px;">
    <h4>
      trackBy
    </h4>
    <div>
      <button (click)="setItem2()">Apply</button>
    </div>
    <div>
      <ul>
        <ng-container *ngFor="let item of item2; trackBy: trackByItem">
          <custom-list [num]="item.id" title="trackBy"></custom-list>
        </ng-container>
      </ul>
    </div>
  </div>
</div>
app.component.ts
import { Component } from "@angular/core";

interface ListData {
  id: number;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent {
  item1: ListData[] = [];
  item2: ListData[] = [];

  setItem1(): void {
    this.item1 = this.createList();
  }

  setItem2(): void {
    this.item2 = this.createList();
  }

  /**
   * ngFor trackBy setting.
   * @param index
   * @param value 
   */
  trackByItem(index: number, value: ListData): number {
    return value ? value.id : null;
  }

  private createList(): ListData[] {
    const items = new Array<ListData>();
    const length = Math.floor(Math.random() * 7) + 3;
    for (let i = 0; i < length; i++) {
      while (true) {
        const value = Math.floor(Math.random() * 10) + 1;
        if (items.findIndex(item => item.id === value) < 0) {
          items.push({ id: value });
          break;
        }
      }
    }
    return items;
  }
}

実行結果

trackBy未使用

f:id:kakkoya:20181229142142g:plain

  • ngForの対象の全てのDOMの生成・破棄が行われているのが分かります。
trackBy使用

f:id:kakkoya:20181229142246g:plain

  • 差分箇所のみDOMの生成・破棄が行われているのが分かります。

サンプルソース一式

github.com