6
votes

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?

3

3 Answers

7
votes

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

4
votes

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.

3
votes

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).