Implementa un rilevamento delle modifiche più rapido per una migliore esperienza utente.
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 dell'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 imparerai a controllare e ottimizzare il meccanismo di rilevamento delle modifiche saltando parti della tua 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 dei due reparti di un'azienda, Vendite e Ricerca e sviluppo, 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 diamo 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 un elenco di dipendenti e il nome di un reparto come input. Quando l'utente tenta di rimuovere o aggiungere un dipendente, il componente attiva un output corrispondente. Il componente definisce anche il metodo calculate
, che implementa il calcolo dell'attività.
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 dell'elenco e, per ognuno, visualizza 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 di un EmployeeListComponent
, 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 "Ctrl+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 pochi 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 ci sono molti dipendenti nell'elenco, questo processo potrebbe bloccare il thread dell'interfaccia utente del browser e causare l'interruzione di frame, con un'esperienza utente negativa.
Ignorare i sottoalberi 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 linee guida 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 pipe pura ricalcola il suo risultato solo se riceve un input diverso dalla sua chiamata precedente.
Ricorda che l'app calcola un valore da visualizzare in base al valore numerico del dipendente, richiamando 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à ricalcoli a meno che non venga aggiornato il valore numerico di un dipendente.
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 barra verticale richiama la funzione fibonacci
. Nota 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 seguente 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 eliminare i sottoalberi di un componente. - Sposta i calcoli intensivi in pipe pure per consentire al framework di eseguire la memorizzazione nella cache dei valori calcolati.