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 dijalankan terlalu sering, hal itu dapat memicu banyak komputasi dan memblokir thread utama browser.

Dalam postingan ini, Anda akan mempelajari cara mengontrol dan mengoptimalkan mekanisme deteksi perubahan dengan melewati bagian-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 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 di 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, terdapat 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 {i>output<i} 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 masing-masing karyawan, merender item daftar. Kode ini juga mencakup perintah ngModel untuk data binding 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 masing-masing karyawan di departemen.

Saat pengguna mulai memasukkan nama karyawan baru di kotak input di EmployeeListComponent, Angular memicu deteksi perubahan untuk seluruh hierarki komponen mulai dari AppComponent. Ini berarti bahwa saat pengguna mengetik input teks, Angular berulang kali menghitung ulang nilai numerik yang terkait dengan setiap karyawan untuk memverifikasi bahwa mereka 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 pelambatan berasal dari fungsi fibonacci dengan menyiapkan example project dan membuka tab Performance di Chrome DevTools.

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

lagi untuk berhenti merekam. Setelah Chrome DevTools memproses semua data pembuatan profil yang dikumpulkannya, Anda akan melihat sesuatu seperti ini:

Profiling performa

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

Melewati sub-hierarki komponen

Ketika pengguna mengetik input teks untuk EmployeeListComponent penjualan, 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 input teks, deteksi perubahan hanya akan dipicu untuk departemen yang sesuai:

Mengubah deteksi pada sub-hierarki komponen

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

Anda dapat membaca lebih lanjut tentang 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 sekarang ditetapkan ke OnPush, Angular masih menghitung ulang nilai numerik untuk semua karyawan di departemen saat pengguna mengetik dalam input teks yang sesuai.

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

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

Berikut cara memindahkan penghitungan bisnis ke pipa bernama 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 itu murni. Angular akan menganggap semua pipe sebagai murni kecuali jika Anda menentukan lain.

Terakhir, perbarui ekspresi di dalam template untuk EmployeeListComponent:

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

Selesai. Sekarang, ketika pengguna mengetik di input teks yang terkait dengan departemen apa pun, aplikasi tidak akan menghitung ulang nilai numerik untuk masing-masing karyawan.

Pada aplikasi di bawah ini, Anda dapat melihat seberapa lancar pengetikan yang terjadi.

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

Kode dengan pengoptimalan pipa murni untuk aplikasi asli tersedia di sini.

Kesimpulan

Saat menghadapi pelambatan runtime di aplikasi Angular:

  1. Buat profil aplikasi dengan Chrome DevTools untuk melihat asal pelambatan.
  2. Memperkenalkan strategi deteksi perubahan OnPush untuk memangkas subhierarki komponen.
  3. Pindahkan komputasi berat ke pipeline murni untuk memungkinkan framework melakukan caching nilai yang dihitung.