I have a very stupid performance issue.
I have a component that uses ngStyle
, and I'd prefer to not rewrite it. But each time I click random input
on the same page (even from another component), ngStyle
recalculates (and do it pretty slow).
Let's say I want to have a table of multiply with dynamic background:
<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>
Then on the same page I want to add several inputs for a some reason:
<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>
Now each time I click on one of those inputs - getBG()
will be called. And even if that function just return a string without any calculations - it's still super slow
Example at StackBlitz - just open consomle and try to click swiftly among different input fields, or enter a value. Even as a user I can see it's not responsive at all
UPD1: My real-world case much more complex. And already use ChangeDetectionStrategy.OnPush
. Binding ngStyle
to a value instead of function also doesn't help much - it faster but still slow (and produces a lot of complexity). What I want, it's probably a way to tell ngStyle
to not recalculate until I'll ask explicitly. Maybe ChangeDetectorRef.detach()
could help
That makes perfect sense. This is how Angular performs change detection. And this is Angular performing extra checks since you called a function in one of the data-binding syntaxes, here:
[ngStyle]="{'background-color': getBG(row*col)}"
Angular performs Change Detection in three cases:
This is the case of DOM Events (click
).
Now when performing Change Detection, Angular check whether a particular variable in the Component has changed.
That's pretty straight forward in case of properties. But not so straight-forward in case of functions.
You see, the only way to determine whether the value of a function has changed is by calling it.
So Angular is doing just that.
Just create a matrix for the number to show and the color to paint right in the Component Class:
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%)';
}
}
And then use it in your template:
<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>
In the previous implementation, the getBG
was called 401 times on initialization.
In the solution implementation, the getBG
is called 101 times on initialization.
That's a massive performance gain of around 397%.
Plus there's no extra call to the getBG
method when the user focuses and blurs out from any input field.
Here's a Working Sample StackBlitz for your ref.
You might also want to read through a Medium Article that I wrote about Performant Reactive Forms in Angular. Although it's related to Reactive Forms, I've touched upon this aspect, in the article. I'm sure you'll find it helpful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With