Implementa un rilevamento delle modifiche più rapido per una migliore esperienza utente.
Angular esegue periodicamente il suo meccanismo di rilevamento delle modifiche per far sì che le modifiche al modello dei dati si riflettano nella vista 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 un'istruzione ngModel
per l'associazione di dati bidirezionali 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 suoi 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 sta digitando l'input di testo, Angular ricalcola ripetutamente i valori numerici associati a ciascun dipendente per verificare che non siano stati modificati dall'ultimo controllo.
Per vedere quanto possa essere lenta questa operazione, 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 nell'app. Tra qualche secondo, fai di nuovo clic su Registra per interrompere la registrazione. Una volta che Chrome DevTools avrà elaborato tutti i dati di profilazione raccolti, vedrai qualcosa del genere:
Se ci sono molti dipendenti nell'elenco, questo processo potrebbe bloccare il thread dell'interfaccia utente del browser e causare l'interruzione dei frame, con un'esperienza utente negativa.
Ignorare i sottoalberi dei componenti
Quando l'utente digita l'input di testo per il EmployeeListComponent
di vendita, sai che i dati del reparto Ricerca e sviluppo non cambiano, quindi non c'è motivo di eseguire il rilevamento delle modifiche sul suo componente. Per assicurarti che l'istanza di ricerca e sviluppo non attivi il rilevamento delle modifiche, imposta 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.
Per saperne di più sulla strategia di rilevamento delle modifiche di OnPush
, consulta la 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 pipeline 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 su una barra verticale pura, Angular ricalcolerà l'espressione barra verticale solo quando i suoi argomenti cambiano. Il framework determinerà se gli argomenti della barra verticale 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 aziendale in una pipeline 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 considera tutte le barre verticali 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 pipe pura dell'applicazione originale è disponibile qui.
Conclusione
In caso di rallentamenti di runtime in un'app Angular:
- Profila l'applicazione con Chrome DevTools per vedere la provenienza dei 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.