Implementa il rilevamento delle modifiche più rapido per un'esperienza utente migliore.
Angular esegue periodicamente il proprio meccanismo di rilevamento delle modifiche in modo che le modifiche al modello di dati vengano applicate alla visualizzazione di un'app. Il rilevamento delle modifiche può essere attivato manualmente o tramite un evento asincrono (ad esempio un'interazione utente o il completamento di un XHR).
Il rilevamento delle modifiche è uno strumento potente, ma se viene eseguito molto spesso, può attivare molti calcoli e bloccare il thread principale del browser.
In questo post scoprirai come controllare e ottimizzare il meccanismo di rilevamento delle modifiche saltando parti dell'applicazione ed eseguendo il rilevamento delle modifiche solo quando necessario.
All'interno del rilevamento delle modifiche di Angular
Per capire come funziona il rilevamento delle modifiche di Angular, diamo un'occhiata a un'app di esempio.
Puoi trovare il codice dell'app in questo repository GitHub.
L'app elenca i dipendenti di due reparti di un'azienda, ovvero vendite e R&D, e ha due componenti:
AppComponent
, che è il componente principale dell'app, e- Due istanze di
EmployeeListComponent
, una per le vendite e una per la ricerca e lo sviluppo.
Puoi vedere le due istanze di EmployeeListComponent
nel modello per 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>
Per ogni dipendente sono presenti un nome e un valore numerico. L'app passa il valore numerico del dipendente a un calcolo aziendale e visualizza il risultato sullo schermo.
Ora dai un'occhiata a 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
accetta come input un elenco di dipendenti e il nome di un reparto. Quando l'utente tenta di rimuovere o aggiungere un dipendente, il componente attiva un'uscita corrispondente. Il componente definisce anche il metodo calculate
, che implementa il calcolo aziendale.
Ecco il modello per 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>
Questo codice esegue l'iterazione su tutti i dipendenti nell'elenco e, per ciascuno, mostra un elemento dell'elenco. Include anche una direttiva ngModel
per il binding dei dati bidirezionale tra l'input e la proprietà label
dichiarata in EmployeeListComponent
.
Con le due istanze di EmployeeListComponent
, l'app forma la seguente struttura ad albero dei componenti:
AppComponent
è il componente principale dell'applicazione. I componenti secondari sono le due istanze di EmployeeListComponent
. Ogni istanza ha un elenco di elementi (E1, E2 e così via) che rappresentano i singoli dipendenti del reparto.
Quando l'utente inizia a inserire il nome di un nuovo dipendente nella casella di immissione in unEmployeeListComponent
, Angular attiva il rilevamento delle modifiche per l'intera struttura ad albero dei componenti a partire da AppComponent
. Ciò significa che mentre l'utente digita l'input di testo, Angular ricalcola ripetutamente i valori numerici associati a ciascun dipendente per verificare che non siano cambiati dall'ultimo controllo.
Per capire quanto può essere lento, apri la versione non ottimizzata del progetto su StackBlitz e prova a inserire il nome di un dipendente.
Puoi verificare che il rallentamento provenga dalla funzione fibonacci
configurando il progetto di esempio e aprendo la scheda Prestazioni di Chrome DevTools.
- Premi "Control+Maiusc+J" (o "Comando+Opzione+J" su Mac) per aprire DevTools.
- Fai clic sulla scheda Rendimento.
Ora fai clic su Registra (nell'angolo in alto a sinistra del riquadro Rendimento) e inizia a digitare in una delle caselle di testo dell'app. Dopo alcuni secondi, fai di nuovo clic su Registra per interrompere la registrazione. Una volta che Chrome DevTools ha elaborato tutti i dati di profilazione raccolti, vedrai qualcosa di simile a quanto segue:
Se nell'elenco sono presenti molti dipendenti, questa procedura potrebbe bloccare il thread dell'interfaccia utente del browser e causare cali di frame, il che comporta un'esperienza utente negativa.
Ignorare gli alberi sottostanti dei componenti
Quando l'utente digita l'input di testo per il EmployeeListComponent
vendite, sai che i dati del reparto R&D non cambiano, quindi non c'è motivo di eseguire il rilevamento delle modifiche sul relativo componente. Per assicurarti che l'istanza R&D non attivi il rilevamento delle modifiche, imposta il changeDetectionStrategy
di EmployeeListComponent
su OnPush
:
import { ChangeDetectionStrategy, ... } from '@angular/core';
@Component({
selector: 'app-employee-list',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush,
styleUrls: ['employee-list.component.css']
})
export class EmployeeListComponent {...}
Ora, quando l'utente digita un input di testo, il rilevamento delle modifiche viene attivato solo per il reparto corrispondente:
Puoi trovare questa ottimizzazione applicata all'applicazione originale qui.
Puoi scoprire di più sulla strategia di rilevamento delle modifiche OnPush
nella documentazione ufficiale di Angular.
Per vedere l'effetto di questa ottimizzazione, inserisci un nuovo dipendente nell'applicazione su StackBlitz.
Utilizzo di pipe pure
Anche se la strategia di rilevamento delle modifiche per EmployeeListComponent
è ora impostata su OnPush
, Angular ricalcola comunque il valore numerico per tutti i dipendenti di un reparto quando l'utente digita l'input di testo corrispondente.
Per migliorare questo comportamento, puoi utilizzare le pipe pure. Sia le pipe pure che quelle impure accettano input e restituiscono risultati che possono essere utilizzati in un modello. La differenza tra i due è che una pipeline pura ricalcola il risultato solo se riceve un input diverso dall'invocazione precedente.
Ricorda che l'app calcola un valore da visualizzare in base al valore numerico del dipendente, chiamando il metodo calculate
definito in EmployeeListComponent
. Se sposti il calcolo in una barra verticale pura, Angular ricalcolerà l'espressione della barra verticale solo quando cambiano i relativi argomenti. Il framework determinerà se gli argomenti della pipeline sono cambiati eseguendo un controllo del riferimento. Ciò significa che Angular non eseguirà alcun ricalcolo a meno che il valore numerico di un dipendente non venga aggiornato.
Ecco come spostare il calcolo dell'attività in una canalizzazione denominata 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);
}
}
Il metodo transform
della pipeline richiama la funzione fibonacci
. Tieni presente che il tubo è puro. Angular considererà tutte le pipe pure, se non diversamente specificato.
Infine, aggiorna l'espressione all'interno del modello per EmployeeListComponent
:
<mat-chip-list>
<md-chip>
{{ item.num | calculate }}
</md-chip>
</mat-chip-list>
È tutto. Ora, quando l'utente digita l'input di testo associato a un reparto, l'app non ricalcola il valore numerico per i singoli dipendenti.
Nell'app di seguito puoi vedere quanto è più fluida la digitazione.
Per vedere l'effetto dell'ultima ottimizzazione, prova questo esempio su StackBlitz.
Il codice con l'ottimizzazione della pipeline pura dell'applicazione originale è disponibile qui.
Conclusione
In caso di rallentamenti di runtime in un'app Angular:
- Esegui il profiling dell'applicazione con Chrome DevTools per capire da dove provengono i rallentamenti.
- Introduci la strategia di rilevamento delle modifiche
OnPush
per potare i sottoalberi di un componente. - Sposta i calcoli complessi in pipe pure per consentire al framework di eseguire la memorizzazione nella cache dei valori calcolati.