function gets called several times
Asked Answered
S

4

22

I want to display a dataList. Some values are calculate from a function. It seems angular2 calls the calculate function many times.

  <tr *ngFor="let data of dataList">
    <td>{{ data.no }}</td>
    <td>{{ data.name }}</td>
    <td>{{ calculateFunction(data.price) }}</td>
  </tr>

Console will output "calculate..." many times, more than dataList.length.

calculateFunction() {
  console.log('calculate...');
  return ...;
}

Should I worry about that for performance or just let angular2 do this?

Supplicant answered 20/7, 2017 at 6:54 Comment(2)
Presumably something is triggering a model change, which causes it to run the function for all the elements again. If it's a lightweight function, no big deal, if it's something more complex, probably better to try to handle it inside the component instead.Ricciardi
@JohnMontgomery There's just one thing trigger model change. dataList is undefined at init state, then assign the http result to dataList when request is finished.Supplicant
H
14

The caculateFunction(data.price) function will be called every time Angular runs change detection for the component (more precisely for the embedded view created by ngFor). This is because updating DOM is part of change detection and Angular needs to call caculateFunction to know what value to use for DOM update. And change detection cycle can run quite often. So if you have 3 items in the list and CD is triggered 3 times initially, you will see that the function is called 9 times.

If you inspect the updateRenderer function, you shoul see something along these lines:

function(_ck,_v) {
    var _co = _v.component;
    var currVal_0 = _co.calculateFunction(_v.context.$implicit);
    _ck(_v,3,0,currVal_0);
  }

Read more about how Angular updates DOM in The mechanics of DOM updates in Angular.

Should I worry about that for performance or just let angular2 do this?

If calculateFunction(data.price) returns the same result for the same price, it's better to calculate these values beforehand for each item and simply renderer them in the template.

<td>{{ data.no }}</td>
<td>{{ data.name }}</td>
<td>{{ data.calculatePrice) }}</t

In this way you will avoid performance decreasing function calls.

Hawkie answered 20/7, 2017 at 7:6 Comment(0)
P
18

You can change the components changeDetectionStrategy to onPush. after that, your calculateFunction function does not call several times.

to do that :

in your component:

@Component({
 selector: 'app-root',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css'],
 changeDetection: ChangeDetectionStrategy.OnPush // this line
})

export class AppComponent {

  ....

  calculateFunction(value) {
    console.log('calculate...');
    return ...;
  }
}

and in your app.component.html :

 <tr *ngFor="let data of dataList">
  <td>{{ data.no }}</td>
  <td>{{ data.name }}</td>
  <td>{{ calculateFunction(data.price) }}</td>
 </tr>

UPDATED

The best way to handle this scenario is to use Pipe, like the following :

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'handlePrice'
})
export class HandlePricePipe implements PipeTransform {
  transform(price: number): number {
    console.log('In Pipe ....');
    return price * 2;
  }
}

Then use that :

<tr *ngFor="let data of dataList">
   <td>{{ data.no }}</td>
   <td>{{ data.name }}</td>
   <td>{{ data.price |handlePrice }}</td>
</tr>
Postfix answered 26/8, 2018 at 15:15 Comment(1)
Thank you so much. The pipe approach is working fine without changing the default behavior of changeDetection. The first solution makes angular acts like a simple static html page.Wellgrounded
H
14

The caculateFunction(data.price) function will be called every time Angular runs change detection for the component (more precisely for the embedded view created by ngFor). This is because updating DOM is part of change detection and Angular needs to call caculateFunction to know what value to use for DOM update. And change detection cycle can run quite often. So if you have 3 items in the list and CD is triggered 3 times initially, you will see that the function is called 9 times.

If you inspect the updateRenderer function, you shoul see something along these lines:

function(_ck,_v) {
    var _co = _v.component;
    var currVal_0 = _co.calculateFunction(_v.context.$implicit);
    _ck(_v,3,0,currVal_0);
  }

Read more about how Angular updates DOM in The mechanics of DOM updates in Angular.

Should I worry about that for performance or just let angular2 do this?

If calculateFunction(data.price) returns the same result for the same price, it's better to calculate these values beforehand for each item and simply renderer them in the template.

<td>{{ data.no }}</td>
<td>{{ data.name }}</td>
<td>{{ data.calculatePrice) }}</t

In this way you will avoid performance decreasing function calls.

Hawkie answered 20/7, 2017 at 7:6 Comment(0)
M
1

Another alternative (more generic than @Abolfazl Roshanzamir solution, which is of course valid) is using a method like you are doing BUT wrapping it in a Pipe.

Example:

@Pipe({
    name: 'wrapFn'
})
/**
 * Wraps a function in a pipe to prevent the function from being called directly in the template.
 */
export class FunctionWrapperPipe implements PipeTransform {
    transform<T>(value: T, func: (...x: any[]) => any, ...args: any[]): ReturnType<typeof func> {
        return func(value, ...args);
    }
}

and using it in your template like this:

...

<td>{{ data.price | wrapFn: calculateFunction }}</td>

...

So, in your component, your method will have to return a callback:

calculateFunction = (price: number): number => { return price * 2 }
Mweru answered 6/3, 2023 at 10:33 Comment(1)
This worked for me. Using ChangeDetectionStrategy.OnPush had undesired effects, so this seems like a cleaner option.Hardaway
S
0
@Component({
.
.
 changeDetection: ChangeDetectionStrategy.OnPush // this line
})
export class MyComponent {
}

adding change detection to the component decorator solved the issue for me.

Sharecropper answered 2/12, 2020 at 17:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.