Search code examples
angularangular2-changedetectionangular-changedetection

Confusing Angular behaviour in *ngFor loop


I'm a bit confused over the behaviour of this loop in Angular: A simple *ngFor loop over an array of 5 fields

<div *ngFor="let field of this.fields" >
  <div class="block">{{ field.title }} : {{getValue(field)}}</div>
</div>

The function getValue is

getValue(field) {
    console.log("getValue: "+ this._counter++, field.title);
    return field.name;
}

Given that the array is just 5 fields I would expect the function getValue to execute only 5 times (or the number of items in the array) but as you can see from this fiddle it executes 20 times when run first, and any change in state makes it run 10 more times. (read from the console)

In fact an array with just 1 item will run 4 times when the page loads and any change of state will cause it to run twice.

This becomes more pronounced in a proper application with an array of hundreds and a complex function that runs to get the data.

This appears to be the same for all versions of Angular (at least the ones I've tested from v4-v16)

So I was wondering. first of all why this executes so many times, and secondly is there a more efficient way to loop through the array given that there is a complex function to return the data?


Solution

  • Each time you write a function into an interpolation in .html, Angular should execute each time to know if "some has changed".

    When you use a variable Angular compare the value of the variable with the old value of it, but if you use a fuinction Angular should execute the function to get the actual value.

    You can minimize using ChangeDetectionStrategy.OnPush (this have more implications, and be sure understands before use or not).

    Equals happens if you write a simple

    {{getValue()}}
    
    getValue()
    {
       return "hello word"
    }
    

    In general it's the reason we we should avoid use functions in .html even more when we have a loop. In case of an array you can write some like

    this.fieldsExtends=this.fields.map(x=>({...x,value:this.getValue(x)}))
    

    and you can iterate over fieldExtends

    <!--NOTE: in .html remove the "this", the only variables that you can show
        are the public variables of your component-->
    
    <div *ngFor="let field of fieldsExtends" >
      <div class="block">{{ field.title }} : field.value}</div>
    </div>