Menerapkan 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 Litbang—dan memiliki dua komponen:
AppComponent
, yang merupakan komponen root aplikasi, dan- Dua instance
EmployeeListComponent
, satu untuk penjualan dan satu untuk R&D.
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, ada nama dan nilai numerik. Aplikasi meneruskan nilai numerik karyawan ke sebuah 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. 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:
AppComponent
adalah komponen root aplikasi. Komponen turunannya adalah dua instance dari 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 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, lalu coba masukkan nama karyawan.
Anda dapat memastikan bahwa pelambatan berasal dari fungsi fibonacci
dengan menyiapkan contoh project dan membuka tab Performa di Chrome DevTools.
- Tekan `Control+Shift+J` (atau `Command+Option+J` di Mac) untuk membuka DevTools.
- Klik tab Performa.
Sekarang klik Rekam (di pojok kiri atas panel Performa) dan mulai ketik di salah satu kotak teks di aplikasi. Dalam beberapa detik, klik Rekam lagi untuk berhenti merekam. Setelah Chrome DevTools memproses semua data pembuatan profil yang dikumpulkannya, Anda akan melihat tampilan seperti ini:
Jika ada banyak karyawan dalam daftar, proses ini dapat memblokir UI thread browser dan menyebabkan penghapusan frame, yang menyebabkan pengalaman pengguna yang buruk.
Melewati subhierarki 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 ketika pengguna mengetik input teks, deteksi perubahan hanya dipicu untuk departemen yang sesuai:
Anda dapat mendapati bahwa pengoptimalan ini 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 pada application on StackBlitz.
Menggunakan pipa murni
Meskipun strategi deteksi perubahan untuk EmployeeListComponent
sekarang disetel 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. Baik pipa murni dan tidak murni menerima input dan menampilkan hasil yang dapat digunakan dalam template. Perbedaan di antara keduanya adalah bahwa pipa murni akan menghitung ulang hasilnya hanya jika ia menerima input yang berbeda dari pemanggilan sebelumnya.
Perlu diingat bahwa aplikasi menghitung nilai yang akan ditampilkan berdasarkan nilai numerik karyawan, dengan memanggil metode calculate
yang ditentukan di EmployeeListComponent
. Jika Anda memindahkan penghitungan ke pipe murni, Angular hanya akan menghitung ulang ekspresi pipe saat argumennya berubah. Kerangka kerja ini akan menentukan apakah argumen pipa telah berubah dengan melakukan pemeriksaan referensi. Ini berarti Angular tidak akan melakukan kalkulasi ulang apa pun 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 ketika pengguna mengetik input teks yang terkait dengan departemen mana pun, aplikasi tidak akan menghitung ulang nilai numerik untuk masing-masing 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 menghadapi pelambatan runtime di aplikasi Angular:
- Buat profil aplikasi dengan Chrome DevTools untuk melihat asal pelambatan.
- Memperkenalkan strategi deteksi perubahan
OnPush
untuk memangkas sub-pohon komponen. - Pindahkan komputasi berat ke pipe murni untuk memungkinkan framework melakukan penyimpanan dalam cache nilai yang dihitung.