javascript - Performanța Angular: ngStyle se recalculază la fiecare clic pe input aleator
Am o problemă de performanță foarte stupidă.
Am un component care folosește ngStyle și aș prefera să nu îl rescriu. Dar de fiecare dată când dau clic pe un input aleatoriu de pe aceeași pagină (chiar și dintr-un alt component), ngStyle se recalculează (și o face destul de lent).
Să zicem că vreau să am un tabel de înmulțire cu fundal dinamic:
<section>
<div class="row"
*ngFor="let row of rows">
<div class="col"
[ngStyle]="{'background-color': getBG(row*col)}"
*ngFor="let col of cols ">
{{row * col}}
</div>
</div>
</section>
Apoi, pe aceeași pagină vreau să adaug mai multe câmpuri de introducere dintr-un anumit motiv:
<section>
<input type="text" [ngModel]="model1"/>
<input type="text"[ngModel]="model2"/>
<input type="text"[ngModel]="model3"/>
<input type="text"[ngModel]="model4"/>
<input type="text"[ngModel]="model5"/>
</section>
Acum, de fiecare dată când fac clic pe unul dintre acele inputuri - getBG() va fi apelat. Și chiar dacă acea funcție doar returnează un șir fără niciun calcul - tot este foarte lentă
Exemplu pe StackBlitz - doar deschide consola și încearcă să dai clic rapid între diferite câmpuri de input sau să introduci o valoare. Chiar și ca utilizator pot vedea că nu este deloc receptiv
UPD1: Cazul meu din lumea reală este mult mai complex. Și deja folosesc ChangeDetectionStrategy.OnPush. Legarea ngStyle de o valoare în loc de funcție nu prea ajută - este mai rapid, dar tot lent (și produce multă complexitate). Ceea ce vreau, probabil, este o modalitate de a spune ngStyle să nu recalculze până nu cer în mod explicit. Poate ChangeDetectorRef.detach() ar putea ajuta
Răspuns #1
Asta are perfect sens. Așa efectuează Angular detectarea schimbărilor. Și acesta este Angular efectuând verificări suplimentare deoarece ai apelat o funcție într-una dintre sintaxele de legare a datelor, aici:
[ngStyle]="{'background-color': getBG(row*col)}"
Angular efectuează detectarea schimbărilor în trei cazuri:
- Evenimente DOM.
- Apeluri AJAX.
- Timeout-uri/Intervale.
Acesta este cazul evenimentelor DOM (click).
Acum, atunci când se efectuează Detectarea Schimbărilor, Angular verifică dacă o anumită variabilă din Componentă s-a schimbat.
Asta e destul de simplu în cazul proprietăților. Dar nu la fel de simplu în cazul funcțiilor.
Vezi tu, singura modalitate de a determina dacă valoarea unei funcții s-a schimbat este să o apelezi.
Deci Angular face chiar asta.
SOLUȚIE :
Doar creează o matrice pentru numărul de afișat și culoarea de folosit direct în Clasa Componentă:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
rows = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
cols = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
matrix = [];
model1 = '';
model2 = '';
model3 = '';
model4 = '';
model5 = '';
ngOnInit() {
this.rows.forEach((row, rowIndex) => {
this.matrix.push([]);
this.cols.forEach((col, colIndex) => {
const product = row * col;
this.matrix[row].push({
numberToShow: product,
color: this.getBG(product),
});
})
});
}
getBG(hue: number): string {
console.log('getBG was called');
return 'hsl(' + hue + ', 100%, 50%)';
}
}
Și apoi folosește-l în șablonul tău:
<br/>
<div> 1. Open a console</div>
<br/>
<section>
<div class="row" *ngFor="let row of matrix">
<div
class="col"
[style.background-color]="col.color"
*ngFor="let col of row ">
{{col.numberToShow}}
</div>
</div>
</section>
<br/>
<div>2. Click fast on the different inputs: </div>
<br/>
<section>
<input type="text" [ngModel]="model1"/>
<input type="text"[ngModel]="model2"/>
<input type="text"[ngModel]="model3"/>
<input type="text"[ngModel]="model4"/>
<input type="text"[ngModel]="model5"/>
</section>
Diferența în performanță:
În implementarea anterioară, getBG a fost apelat de 401 ori la inițializare.
În implementarea soluției, getBG este apelat de 101 ori la inițializare.
Aceasta este o creștere masivă a performanței de aproximativ 397%.
În plus, nu există un apel suplimentar la metoda getBG atunci când utilizatorul focalizează și iese din orice câmp de introducere.
Iată un exemplu de lucru StackBlitz pentru referința ta.
S-ar putea să doriți, de asemenea, să citiți un articol pe Medium pe care l-am scris despre Formulare Reacative Performante în Angular. Deși este legat de Formulare Reacative, am atins acest aspect în articol. Sunt sigur că îl veți găsi util.
Răspunsul #2
Detectarea este lentă din 2 motive. Unealta de dezvoltare este destul de lentă și tipărirea multor mesaje poate arăta lucrurile și mai mult.
Celălalt aspect este că faci muncă inutilă. Separând cele două părți, vei putea schimba strategia changeDetection în OnPush.
Exemplu simplificat:
@Component({
selector: 'my-cell',
template: '<div [ngStyle]="styles"><ng-content></ng-content></div>',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CellComponent {
@Input() styles: {
readonly "background-color": string;
};
}
și
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
rows = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
cols = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
matrix: {
numberToShow: number;
styles: {
readonly "background-color": string;
};
}[][] = [];
model1 = '';
model2 = '';
model3 = '';
model4 = '';
model5 = '';
ngOnInit() {
this.rows.forEach((row, rowIndex) => {
this.matrix.push([]);
this.cols.forEach((col, colIndex) => {
const product = row * col;
const self = this;
this.matrix[row].push({
numberToShow: product,
styles: {
get "background-color"() {
console.log('background-color read');
return self.getBG(product)
},
},
});
})
});
}
getBG(hue: number): string {
return 'hsl(' + hue + ', 100%, 50%)';
}
}
<section>
<div class="row" *ngFor="let row of matrix">
<my-cell [styles]="col.styles" *ngFor="let col of row">
{{col.numberToShow}}
</my-cell>
</div>
</section>
<section>
<input type="text" [ngModel]="model1"/>
<input type="text" [ngModel]="model2"/>
<input type="text" [ngModel]="model3"/>
<input type="text" [ngModel]="model4"/>
<input type="text" [ngModel]="model5"/>
</section>
Strategia de detectare OnPush înseamnă că, dacă oricare dintre @Input-urile unui anumit Component/Directive se schimbă, aceasta va declanșa detectarea. Dacă vrei să folosești asta, trebuie să separi partea costisitoare într-o directivă separată, asigurându-te totodată că @Input-urile acesteia se vor schimba doar dacă este necesar.
StackBlitz: https://stackblitz.com/edit/style-performance-of-a-grid-fzbzkz
