Mengoptimalkan deteksi perubahan Angular

Terapkan deteksi perubahan yang lebih cepat untuk pengalaman pengguna yang lebih baik.

Angular menjalankan mekanisme deteksi perubahannya secara berkala sehingga perubahan pada model data tercermin dalam tampilan aplikasi. Deteksi perubahan dapat dipicu secara manual atau melalui peristiwa asinkron (misalnya, interaksi pengguna atau penyelesaian XHR).

Deteksi perubahan adalah alat yang canggih, tetapi jika dijalankan terlalu sering, deteksi ini dapat memicu banyak komputasi dan memblokir thread browser utama.

Dalam postingan ini, Anda akan mempelajari cara mengontrol dan mengoptimalkan mekanisme deteksi perubahan dengan melewati bagian aplikasi dan menjalankan deteksi perubahan hanya jika diperlukan.

Di dalam deteksi perubahan Angular

Untuk memahami cara kerja deteksi perubahan Angular, mari kita lihat aplikasi contoh.

Anda dapat menemukan kode untuk aplikasi di repositori GitHub ini.

Aplikasi ini mencantumkan karyawan dari dua departemen di sebuah perusahaan—penjualan dan R&D—dan memiliki dua komponen:

  • AppComponent, yang merupakan komponen root aplikasi, dan
  • Dua instance EmployeeListComponent, satu untuk penjualan dan satu untuk R&D.

Contoh aplikasi

Anda dapat melihat dua instance EmployeeListComponent dalam template untuk AppComponent:

<app-employee-list
  [data]="salesList"
  department="Sales"
  (add)="add(salesList, $event)"
  (remove)="remove(salesList, $event)"
></app-employee-list>

<app-employee-list
  [data]="rndList"
  department="R&D"
  (add)="add(rndList, $event)"
  (remove)="remove(rndList, $event)"
></app-employee-list>

Untuk setiap karyawan, ada nama dan nilai numerik. Aplikasi meneruskan nilai numerik karyawan ke perhitungan bisnis dan memvisualisasikan hasilnya di layar.

Sekarang lihat EmployeeListComponent:

const fibonacci = (num: number): number => {
  if (num === 1 || num === 2) {
    return 1;
  }
  return fibonacci(num - 1) + fibonacci(num - 2);
};

@Component(...)
export class EmployeeListComponent {
  @Input() data: EmployeeData[];
  @Input() department: string;
  @Output() remove = new EventEmitter<EmployeeData>();
  @Output() add = new EventEmitter<string>();

  label: string;

  handleKey(event: any) {
    if (event.keyCode === 13) {
      this.add.emit(this.label);
      this.label = '';
    }
  }

  calculate(num: number) {
    return fibonacci(num);
  }
}

EmployeeListComponent menerima daftar karyawan dan nama departemen sebagai input. Saat pengguna mencoba menghapus atau menambahkan karyawan, komponen akan memicu output yang sesuai. Komponen ini juga menentukan metode calculate, yang menerapkan penghitungan bisnis.

Berikut template untuk EmployeeListComponent:

<h1 title="Department">{{ department }}</h1>
<mat-form-field>
  <input placeholder="Enter name here" matInput type="text" [(ngModel)]="label" (keydown)="handleKey($event)">
</mat-form-field>
<mat-list>
  <mat-list-item *ngFor="let item of data">
    <h3 matLine title="Name">
      {{ item.label }}
    </h3>
    <md-chip title="Score" class="mat-chip mat-primary mat-chip-selected" color="primary" selected="true">
      {{ calculate(item.num) }}
    </md-chip>
  </mat-list-item>
</mat-list>

Kode ini melakukan iterasi pada semua karyawan dalam daftar dan, untuk setiap karyawan, merender item daftar. Contoh ini juga mencakup direktif ngModel untuk binding data dua arah antara input dan properti label yang dideklarasikan di EmployeeListComponent.

Dengan dua instance EmployeeListComponent, aplikasi membentuk hierarki komponen berikut:

Hierarki komponen

AppComponent adalah komponen root aplikasi. Komponen turunannya adalah dua instance EmployeeListComponent. Setiap instance memiliki daftar item (E1, E2, dll.) yang mewakili setiap karyawan di departemen.

Saat pengguna mulai memasukkan nama karyawan baru di kotak input dalamEmployeeListComponent, Angular akan memicu deteksi perubahan untuk seluruh hierarki komponen yang dimulai dari AppComponent. Artinya, saat pengguna mengetik di input teks, Angular akan berulang kali menghitung ulang nilai numerik yang terkait dengan setiap karyawan untuk memverifikasi bahwa nilai tersebut tidak berubah sejak pemeriksaan terakhir.

Untuk melihat seberapa lambatnya, buka versi project yang tidak dioptimalkan di StackBlitz dan coba masukkan nama karyawan.

Anda dapat memverifikasi bahwa perlambatan berasal dari fungsi fibonacci dengan menyiapkan contoh project dan membuka tab Performa di Chrome DevTools.

  1. Tekan `Control+Shift+J` (atau `Command+Option+J` di Mac) untuk membuka DevTools.
  2. Klik tab Performa.

Sekarang klik Rekam (di sudut kiri atas panel Performa) dan mulai mengetik di salah satu kotak teks di aplikasi. Dalam beberapa detik, klik Rekam lagi untuk menghentikan perekaman. Setelah Chrome DevTools memproses semua data pembuatan profil yang dikumpulkannya, Anda akan melihat sesuatu seperti ini:

Profiling performa

Jika ada banyak karyawan dalam daftar, proses ini dapat memblokir thread UI browser dan menyebabkan frame drop, yang menyebabkan pengalaman pengguna yang buruk.

Melewati subpohon komponen

Saat pengguna mengetik di input teks untuk sales EmployeeListComponent, Anda tahu bahwa data di departemen R&D tidak berubah—jadi tidak ada alasan untuk menjalankan deteksi perubahan pada komponennya. Untuk memastikan instance R&D tidak memicu deteksi perubahan, tetapkan changeDetectionStrategy dari EmployeeListComponent ke OnPush:

import { ChangeDetectionStrategy, ... } from '@angular/core';

@Component({
  selector: 'app-employee-list',
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrls: ['employee-list.component.css']
})
export class EmployeeListComponent {...}

Sekarang, saat pengguna mengetik di input teks, deteksi perubahan hanya dipicu untuk departemen yang sesuai:

Deteksi perubahan di subpohon komponen

Anda dapat menemukan pengoptimalan ini diterapkan pada aplikasi asli di sini.

Anda dapat membaca lebih lanjut strategi deteksi perubahan OnPush di dokumentasi Angular resmi.

Untuk melihat efek pengoptimalan ini, masukkan karyawan baru di aplikasi di StackBlitz.

Menggunakan pipa murni

Meskipun strategi deteksi perubahan untuk EmployeeListComponent kini ditetapkan ke OnPush, Angular masih menghitung ulang nilai numerik untuk semua karyawan di departemen saat pengguna mengetik di input teks yang sesuai.

Untuk meningkatkan perilaku ini, Anda dapat memanfaatkan pure pipe. Pipe murni dan tidak murni menerima input dan menampilkan hasil yang dapat digunakan dalam template. Perbedaan antara keduanya adalah bahwa pipe murni akan menghitung ulang hasilnya hanya jika menerima input yang berbeda dari pemanggilan sebelumnya.

Ingatlah bahwa aplikasi menghitung nilai yang akan ditampilkan berdasarkan nilai numerik karyawan, dengan memanggil metode calculate yang ditentukan dalam EmployeeListComponent. Jika Anda memindahkan penghitungan ke pipe murni, Angular akan menghitung ulang ekspresi pipe hanya saat argumennya berubah. Framework akan menentukan apakah argumen pipe telah berubah dengan melakukan pemeriksaan referensi. Artinya, Angular tidak akan melakukan penghitungan ulang apa pun kecuali nilai numerik untuk karyawan diperbarui.

Berikut cara memindahkan perhitungan bisnis ke saluran yang disebut CalculatePipe:

import { Pipe, PipeTransform } from '@angular/core';

const fibonacci = (num: number): number => {
  if (num === 1 || num === 2) {
    return 1;
  }
  return fibonacci(num - 1) + fibonacci(num - 2);
};

@Pipe({
  name: 'calculate'
})
export class CalculatePipe implements PipeTransform {
  transform(val: number) {
    return fibonacci(val);
  }
}

Metode transform dari pipe memanggil fungsi fibonacci. Perhatikan bahwa pipa tersebut murni. Angular akan menganggap semua pipe murni kecuali jika Anda menentukannya lain.

Terakhir, perbarui ekspresi di dalam template untuk EmployeeListComponent:

<mat-chip-list>
  <md-chip>
    {{ item.num | calculate }}
  </md-chip>
</mat-chip-list>

Selesai. Sekarang, saat pengguna mengetik input teks yang terkait dengan departemen mana pun, aplikasi tidak akan menghitung ulang nilai numerik untuk setiap karyawan.

Di aplikasi di bawah, Anda dapat melihat betapa lebih lancarnya pengetikan.

Untuk melihat efek pengoptimalan terakhir, coba contoh ini di StackBlitz.

Kode dengan pengoptimalan pipe murni aplikasi asli tersedia di sini.

Kesimpulan

Saat menghadapi perlambatan runtime di aplikasi Angular:

  1. Buat profil aplikasi dengan Chrome DevTools untuk melihat penyebab perlambatan.
  2. Memperkenalkan strategi deteksi perubahan OnPush untuk memangkas subpohon komponen.
  3. Pindahkan komputasi berat ke pure pipe agar framework dapat melakukan caching nilai yang dihitung.