Angular2 template expression called twice for each component on change-detection

1.7k views Asked by At

Pretty standard situation.

There is one parent component <item-list>. Inside its template with *ngFor generated 20 child components <item-block>. Child component styles set with [ngStyle] directive and template expression that calls function setStyles().

The problem (or maybe not) is that when any event emitted on one specific child element, expression setStyles() executed twice for each of child components.

So if we click on one specific item in our example, and we have 20 <item-block> components - setStyles() will be executed 20+20 times.

The questions are:

  1. Why its happening and is it expected behavior.
  2. How it affect performance
  3. How it could be avoided - only one call per child component/detection change.

Example & plnkr:

plnkr (click on item - open console for debug output)

import {Component} from '@angular/core'

@Component({
  selector: 'item-list',
  template: `
    <item-block
        [item]="item"
        *ngFor="let item of items"
    ></item-block>
  `,
})
export class ItemListComponent {

  items: any[] = [];

  constructor() {}

  ngOnInit() {
     // generate dummy empty items
    for (let i = 0; i < 20; i++) {
      this.items.push(
        {
          value: 'item #' + i; 
        }
      )
    }
  }
}

import {Component, Input} from '@angular/core'

@Component({
  selector: 'item-block',
  template: `
    <div
      class="item"
      [ngStyle]="setStyles()"
      (click)="testClick($event)"
    >{{item.value}}</div>
  `,
})
export class ItemBlockComponent {

  @Input() item: any;

  constructor() {}

  testClick(): void{
      console.log('item clicked');
  }

  setStyles(){
      console.log('seting styles...');
      return {
          'background': '#ccc'
      };
  }
}
2

There are 2 answers

13
Günter Zöchbauer On BEST ANSWER
[ngStyle]="setStyles()"

causes setStyles to be called every time change detection is run (which can be quite often and will hurt performance). Also because setStyles() returns a different object instance every time, it should cause an exception. "Expression changed since it was last checked" or similar.

Calling methods from the view this way is discouraged.

Instead assign the value to a property and bind to that property:

[ngStyle]="myStyles"
0
piecioshka On

In default (development mode) Angular run Detect Changes mechanism twice.
In production mode it is reduce to single change.

How to switch Detect Changes mechanism to production?

In main.ts file try to add:

import { enableProdMode } from '@angular/core';
// ...
enableProdMode();
// ...
platformBrowserDynamic().bootstrapModule(AppModule)

and reload application.