Francesco
Francesco

Reputation: 10830

Async Pipe in Template inside ngFor block triggers http GET calls loop

I have the following component Template:

<div *ngFor="let ctrl of data; trackBy:ctrl?.Id">
   <div *ngIf="getNext(ctrl.nextDate) | async as next">
        <span>{{next | date: 'dd.MM.yyyy'}}</span>
   </div>
</div>

getNext() is a simple method returning an Observable<Date>:

public getNext(deadline: string): Observable<Date> {
   return this.http.get<Date>(`${this.config.apiEndpoint}/api/meeting?deadline=${deadline}`);
}

My goal would be to invoke the method and subscribe to the observable with the async pipe in the template. However when I run the application endless GET and OPTIONS requests are generated.

Also if I place the method call outside the ngFor the same happen. The call would need to be executed inside the ngFor as the parameter is different for each collection item.

Why the method is simply called once and no more calls generated after the subscription?

Upvotes: 6

Views: 4373

Answers (3)

Victor No&#235;l
Victor No&#235;l

Reputation: 862

Most certainly your problem is related to change detection.

Everytime angular considers there can be changes to what is necessary to draw your template (i.e. anytime there is a browser event except if your component is OnPush), it will redraw the component, and thus retrigger the loop and the observable.

In that case you have two choices:

  • ensure change detection is not triggered when not needed (for example by making your component follow the OnPush ChangeDetectionStrategy) but it mostly work only if there is a limited set of @Input() that triggers the update of the component.
  • do the requests only once in ngOnInit or in ngOnChanges (in the case data is an @Input() of your component) and store the results in an array that you base your template on to do the for loop (I would go this way).

Upvotes: 3

Boris Lobanov
Boris Lobanov

Reputation: 2454

Calling functions in template is usually not a very good idea as it leads to unpredictable results. This is how you can restructure your code to avoid this:

data: any = [....] // some data
data$: Observable[];

ngOnInit() {
    this.data$ = this.data.map(elem => this.getNext(elem));
} 

public getNext(deadline: string): Observable<Date> {
   return this.http.get<Date>(`${this.config.apiEndpoint}/api/meeting?deadline=${deadline}`);
}

And in your template:

<div *ngFor="let ctrl of data$">
   <div *ngIf="ctrl | async as next">
        <span>{{next | date: 'dd.MM.yyyy'}}</span>
   </div>
</div>

Here's a stackblitz I created where you can see how a similar mechanism works: https://stackblitz.com/edit/angular-nyn4qz

Upvotes: 7

Anton Stepanenkov
Anton Stepanenkov

Reputation: 1036

Angular calls getNext every event cycle, and each time getNext makes new http request and returns new Observable. You need to cache Observable from first function call. I recommend you to create them somewhere in controller, and then pass in template as variables.

Upvotes: 4

Related Questions