The Angular performance may be impacted by the constant recalculation of ngStyle when clicking on various input fields

I am facing a frustrating performance issue.

Within my component, I have implemented ngStyle and I would rather not rewrite it. However, every time I interact with random input fields on the same page (even from another component), the ngStyle recalculates slowly.

For example, I want to create a table displaying the product of two numbers with dynamic background colors:

<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>

However, when I add several input fields to the page:

<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>

Each click on these inputs triggers a call to getBG(), resulting in sluggish performance. This is evident even if the function simply returns a string without any complex calculations.

To see this issue in action, check out the Example at StackBlitz. Open the console and try clicking swiftly among different input fields or entering values - the responsiveness is notably lacking.


UPD1: My scenario involves a more intricate setup, and I already employ ChangeDetectionStrategy.OnPush. Even binding ngStyle directly to a value instead of a function does not significantly improve performance, as it remains slow and introduces complexity. Ideally, I seek a way to instruct ngStyle not to recalculate unless explicitly requested. Perhaps leveraging ChangeDetectorRef.detach() could provide some assistance.

Answer №1

It all adds up perfectly. This is the method by which Angular conducts change detection. Additionally, Angular executes extra checks when a function is invoked within one of the data-binding syntaxes, like so:

[ngStyle]="{'background-color': getBG(row*col)}"

Angular carries out Change Detection in three scenarios:

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

This specific scenario involves DOM Events (click).

During Change Detection, Angular examines whether a certain variable in the Component has been altered.

This process is straightforward for properties but not as simple with functions.

The only way to determine if a function's value has changed is by calling it.

Hence, Angular performs this action.

SOLUTION:

To resolve this issue, establish a matrix in the Component Class that specifies the number to display and the color to use:

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%)';
  }

}

Then, incorporate it into 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 setup, getBG was triggered 401 times upon initialization.

However, with the new implementation, getBG is only called 101 times during initialization.

This results in a significant performance enhancement of approximately 397%.

Besides, there are no additional calls to the getBG method when the user interacts with input fields.

Feel free to explore a Live Example on StackBlitz. It could be beneficial for reference.

You may also wish to peruse my Medium Article about Improving Performance of Reactive Forms in Angular. While centered on Reactive Forms, the article covers related aspects as well. I trust you will find it valuable.

Answer №2

Two key factors contribute to the slow detection process. Firstly, the sluggishness of development tools and the excessive printing of messages can further delay the process.

Secondly, unnecessary work is being done which hinders efficiency. By segregating tasks into distinct parts, it becomes feasible to switch the changeDetection strategy to OnPush.


To illustrate this concept, consider the following simplified example:

@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;
  };
}

and

@Component({
    selector: 'my-app',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    // Implementation details...
}

// Additional code snippets...

The OnPush detection strategy ensures that any changes in the @Inputs of a Component/Directive will trigger detection. By separating resource-intensive tasks into separate directives and ensuring that their @Inputs only change when necessary, optimal performance can be achieved.


Explore the provided StackBlitz for a live example: https://stackblitz.com/edit/style-performance-of-a-grid-fzbzkz

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Running NPM module via Cordova

After developing an app using cordova, I encountered a challenge where I needed to incorporate a node module that lacked a client-side equivalent due to file write stream requirements. My solution involved utilizing Cordova hooks by implementing an app_run ...

Error in React-router: Unable to assign value to the 'props' property as it is undefined

Currently, I am working on setting up routing with Meteor using the react-router package. However, I have encountered a specific TypeError: Here is a link to an image that provides more context: This is the code snippet from my main.js file: import Reac ...

Innovative and interactive animated data display

I've taken inspiration from an example and expanded the code to achieve the following: Dynamic height adjustment Accessibility without JavaScript enabled Is this implementation correct? Can it be expected to function properly on most browsers? You ...

Contrasting the provision of ComponentStore directly versus utilizing provideComponentStore in Angular standalone components

As I delve into the realm of standalone Angular components and tinker with the NgRx Component Store, I find myself grappling with a dilemma on how to properly inject the component store into my component. Here's the current approach I've been usi ...

To access the value of a DOM input in an Angular component, utilize the "renderer/renderer2" method

Recently, I embarked on my journey to learn Angular. One of my goals is to retrieve data from a form and store it in a database (Firebase) using angularfire2. While going through the documentation, I noticed that there is a "setValue()" method available b ...

Unable to add data to an Array once subscribed to BehaviorSubject

Hello everyone, this is my first time asking a question here. I hope it's clear enough for you to understand :) Let's dive straight into the issue at hand. I have a query regarding a behaviorSubject variable in TypeScript that is not allowing me ...

Struggling with getting render props to work in Next.js version 13

Looking to develop a custom component for Contentful's next 13 live preview feature in the app directory, I thought of creating a client component that can accept a data prop and ensure type safety by allowing a generic type to be passed down. Here is ...

Exploring Next.js with getStaticPaths for multi-language support

In my current Next.js project, I am working on implementing multiple locales for dynamic pages using i18n. Within my next.config.js file, the following configuration is set: module.exports = { i18n: { locales: ["en-US", "da-DK", "se-SE", "no-NO", "n ...

Encountered an unexpected symbol < in JSON while implementing fetch() operation

I'm currently working on linking my React signup page to my Django API in order to automatically create a user profile in Django when a user signs up. Whenever I attempt to create a new user, I encounter this error in my console: Signup.js:33 ...

Event triggered when a Socket.IO connection is disconnected

Everything seems to be working well with my code when a new user joins, but I'm encountering issues when a user leaves as it doesn't display anything. Can someone point out the error in my code? Below is my server-side code: const express = requ ...

Guide on utilizing TypeScript declarations imported as `* as`

Currently, I am working with react-icons and attempting to import all icon definitions using the syntax import * as Icons from 'react-icons/fi'. However, I am facing a dilemma on how to create a type that must be one of the types exported from Ic ...

A guide on mapping an array and removing the associated element

I have an array called responseData, which is used to display the available card options on the screen. public responseData = [ { id: 1399, pessoa_id: 75898, created_at: '2022-11-08T16:59:59.000000Z', holder: 'LEONARDO ', validade: ...

Exploring the depths: Retrieving nested object attributes using ngFor

Trying to display the "type" value in the ngFor table resulted in receiving object '[object Object]' of type 'object.' NgFor only supports binding to Iterables such as Arrays. json [{ "id": 123, "name": "Paul", "cars": { ...

An alternative method for storing data in HTML that is more effective than using hidden fields

I'm trying to figure out if there's a more efficient method for storing data within HTML content. Currently, I have some values stored in my HTML file using hidden fields that are generated by code behind. HTML: <input type="hidden" id="hid1 ...

Using Jquery to dynamically add an active class to a link if it matches the current URL

Is there a way to modify the code below so that only the exact current URL will have the active class added? Currently, even when the URL is http://localhost/traineval/training, it also adds the active class to http://localhost/traineval/training/all_train ...

Utilize DataTables with a custom search form for enhanced data filtering

I rely on DataTables for its advanced functionality in creating tables with paging capabilities. When submitting data through a standard jQuery Ajax request using my own form, the returned data is formatted and then passed to the DataTables function to en ...

Using API calls to update component state in React-Redux

I am currently working on setting up a React application where clicking on a map marker in one component triggers the re-rendering of another component on the page. This new component should display data retrieved from a database and update the URL accordi ...

The Angular Validator Pattern may be effective in HTML, but it seems to encounter limitations when

In the world of HTML, Regular Expressions can be quite useful as demonstrated in the example below: <input type="text" formControlName="mgmtIP" class="input-text" pattern="([01]?\d\d?|2[0-4]\d|25[0-5])\.([01]?\d\d?|2[0-4]& ...

Show a Toast in React without relying on useEffect to manage the state

I have successfully implemented the Toast functionality from react-bootstrap into my application using the provided code. However, I am unsure if it is necessary to utilize useEffect to set show with setShow(items.length > 0);. Would it be simpler to ...

Anticipating the outcome of various observables within a loop

I'm facing a problem that I can't seem to solve because my knowledge of RxJs is limited. I've set up a file input for users to select an XLSX file (a spreadsheet) in order to import data into the database. Once the user confirms the file, v ...