Mengoptimalkan deteksi perubahan Angular

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

Angular menjalankan mekanisme deteksi perubahan 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 sering dijalankan, deteksi perubahan 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 contoh aplikasi.

Anda dapat menemukan kode untuk aplikasi di repositori GitHub ini.

Aplikasi ini mencantumkan karyawan dari dua departemen di perusahaan—penjualan dan riset dan pengembangan—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 penghitungan 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 adalah 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. Ini juga mencakup perintah 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 diEmployeeListComponent, Angular akan memicu deteksi perubahan untuk seluruh hierarki komponen mulai dari AppComponent. Artinya, saat pengguna mengetik input teks, Angular berulang kali menghitung ulang nilai numerik yang terkait dengan setiap karyawan untuk memverifikasi bahwa nilai tersebut tidak berubah sejak pemeriksaan terakhir.

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

Anda dapat memverifikasi bahwa pelambatan 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 Record (di pojok kiri atas panel Performance) dan mulai ketik di salah satu kotak teks di aplikasi. Dalam beberapa detik, klik Record lagi untuk berhenti merekam. Setelah Chrome DevTools memproses semua data pembuatan profil yang dikumpulkannya, Anda akan melihat tampilan seperti ini:

Profiling performa

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

Melewati sub-pohon komponen

Saat pengguna mengetik input teks untuk EmployeeListComponent penjualan, Anda tahu bahwa data di departemen R&D tidak berubah—sehingga 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 input teks, deteksi perubahan hanya dipicu untuk departemen yang sesuai:

Deteksi perubahan dalam sub-pohon komponen

Anda dapat menemukan pengoptimalan ini yang diterapkan ke 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 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 hanya akan menghitung ulang hasilnya jika menerima input yang berbeda dari pemanggilan sebelumnya.

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

Berikut cara memindahkan penghitungan bisnis ke pipa 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 pipa memanggil fungsi fibonacci. Perhatikan bahwa pipa tersebut murni. Angular akan menganggap semua pipa murni kecuali jika Anda menentukan sebaliknya.

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 seberapa lancar pengetikan.

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

Kode dengan pengoptimalan pipe murni dari aplikasi asli tersedia di sini.

Kesimpulan

Saat mengalami pelambatan runtime di aplikasi Angular:

  1. Buat profil aplikasi dengan Chrome DevTools untuk melihat asal pelambatan.
  2. Memperkenalkan strategi deteksi perubahan OnPush untuk memangkas sub-pohon komponen.
  3. Pindahkan komputasi berat ke pipe murni untuk memungkinkan framework melakukan penyimpanan dalam cache nilai yang dihitung.