Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
250 views
in Technique[技术] by (71.8m points)

javascript - Angular performance: ngStyle recalculates on each click on random input

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

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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:

  1. DOM Events.
  2. AJAX Calls.
  3. Timeouts / Intervals.

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.

SOLUTION:

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>

Difference in the performance:

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...