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.
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:
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.
- Tekan `Control+Shift+J` (atau `Command+Option+J` di Mac) untuk membuka DevTools.
- 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:
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:
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:
- 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.