*ngFor using a function, returns a loop
Asked Answered
B

3

10

When i use *ngFor in angular with a function returning my data, the function is called multiple times, and sometimes resulting even in a loop:

app.component.ts

export class AppComponent {

 getArray(): string[] {

   //here i know when this function is called
   console.log('getArray called')

   return ['number one', 'number two']
 }

}

app.component.html

<h1 *ngFor="let item of getArray()">
 {{ item }}
</h1>

My console:

enter image description here

Then i get the function getArray() called multiple times and i dont know why.

Barm answered 18/4, 2019 at 3:36 Comment(1)
in my app.component.html, first line.Barm
S
10

Update

You see that it's called multiply time because Angular evaluates all expressions you're using in your template on every change detection cycle. The change detection cycle starts with ApplicationRef.tick method.

When application starts it calls that tick method immediately and then it's managed by ngZone.onMicrotaskEmpty subscription.

Additionaly, every tick method executes additional check checkNoChanges for dev mode.

So you're getting

App starts
   loadComponent
      tick
        checkChanges
              evaluate getArray()
        checkNoChanges
              evaluate getArray()
  ngZone.onMicrotaskEmpty
      subscribe(all promised have been executed)
         tick
           checkChanges
              evaluate getArray()
           checkNoChanges
              evaluate getArray()

      ...some time later
      subscribe(you click somewhere)
         tick
           checkChanges
              evaluate getArray()
           checkNoChanges
              evaluate getArray()
      subscribe(you make a http request)
         tick
           checkChanges
              evaluate getArray()
           checkNoChanges
              evaluate getArray()

Previous answer

You should avoid using expressions in Angular template that execute complex calcultation or perform side effect or return new value on every change detection run.

Particularly in your code

<h1 *ngFor="let item of getArray()">

you're returning a new array on every template check. And ngForOf directive detects that you changed array and tries to rerender it(if your items would be an objects).

It's better if you define that array once in your code.

arr = ['number one', 'number two']

<h1 *ngFor="let item of arr">

Another way that can work for ngForOf directive is using trackBy but it would better to have some unique key in item for that.

See also

Socialization answered 18/4, 2019 at 3:56 Comment(2)
This explains the loop. I will change my strategy on code.Barm
An where should this arr be created?Stocky
D
2

@Yurzui's answer is actually not entierly correct. Here is an example: https://stackblitz.com/edit/angular-uqahdx

It's called multiple times because of the way the Angular lifecycle hooks work. This is the console.log of the page loading when all of the lifecycle hooks are rigged up:

ngOnInit
ngDoCheck                       <!-- Detect and act upon changes that Angular can't or won't detect on its own.
ngAfterContentInit
ngAfterContentChecked           <!-- This is where the *ngFor ng-template is injected and getArray() is evaluated.
!>   getArray called               
ngAfterViewInit
ngAfterViewChecked              <!-- Angular ensures that the data hasn't changed between when the view "compilation" started and ended.
!>   getArray called               
ngDoCheck                       <!-- Angular then does an immediate pass over data bound elements 
ngAfterContentChecked           <!-- Angular has to call getArray yet again because the array reference in memory has changed as we are returning a new array. (like what @Yurzui said)
!>   getArray called               
ngAfterViewChecked              <!-- Angular runs the checking process again. This is where people get that "ExpressionChangedAfterItHasBeenCheckedError" error.
!>   getArray called            

As you can see, these logs align with your screenshot with the 4 calls to getArray().

Dijon answered 18/4, 2019 at 4:22 Comment(0)
S
-1

if you want to make manipulations in your "item" , you can use angular pipe

Sanative answered 17/3, 2022 at 7:58 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Summersummerhouse

© 2022 - 2024 — McMap. All rights reserved.