Using async pipe in template causes multiple requests in Angular 11
Asked Answered
D

3

12

I'm doing a web application in Angular v11.0.2. I need to do a HTTP call to show some information on screen. To save time and to take advantage of the async pipe benefits, I'm using it in the template, but, it's causing multiple requests that never stops.

Service

export class MyDataService {

  constructor(
    private http: HttpClient,
  ) {
  }

  fetchMyEmployeeData(): Observable<any[]> {
    return this.http.post<any[]>('ENDPOINT', {});
  }

}

Component

export class MyAwesomeComponent {

  constructor(
    public myDataService: MyDataService,
  ) {
  }

}

Template

<ng-container *ngIf="(myDataService.fetchMyEmployeeData() | async) as data">
</ng-container>

This causes multiple requests and never stops.

The same happens if I use *ngFor:

<tr *ngFor="let d of (myDataService.fetchMyEmployeeData() | async)">
  <td>.</td>
</tr>

I have tried the following this:

Using the shareReplay operator:

fetchMyEmployeeData(): Observable<any[]> {
   return this.http.post<any[]>('ENDPOINT', {}).pipe(shareReplay());
}

Using a simple div:

<div *ngIf="(myDataService.fetchMyEmployeeData() | async) as data">
</div>

I know that if I subscribe from the component and save the results in a local variable, I can call it in the template, but this is not my goal. Working example:

export class MyAwesomeComponent implements OnInit {

  data: any[];

  constructor(
    public myDataService: MyDataService
  ) {
  }

  ngOnInit() {
    // This is not my goal because I will need to handle the subscription life manually.
    this.myDataService.fetchMyEmployeeData().subscribe(res => this.data = res);
  }

}

I have also followed the recommendations given here:

  1. Multiple identical async pipe in Angular causing multiple http requests
  2. How can I prevent the Angular async pipe from making frequent server calls when no results come back?

I don't know exactly what causes this multiple requests and how can I avoid it.

My goal is to use the async pipe in the template and do just one HTTP call.

Diapophysis answered 4/12, 2020 at 14:25 Comment(5)
Imagine that you wrote code like this myDataService.fetchMyEmployeeData().subscribe(); myDataService.fetchMyEmployeeData().subscribe() in your ts file. Try to debug it. You will find out that each fetchMyEmployeeData() execution returns a new ObservableDaloris
@Daloris it does. So you are saying that what I'm trying to do returns multiple observables and that's why I see multiple requests? Make sense but now I don't understand how others are able to use async pipe in templates without this behavior. I mean, an advantage to use the async in template is to avoid handling the life of the subscription in the component. There is also a few tutorials out there that shows the same technique without experimenting my issue. What do you recommend? I'm confused.Diapophysis
Define observable as a property like employeeData$ = this.myDataService.fetchMyEmployeeData() in your component and you will be happy with employeeData$ | async in template.Daloris
@andriishupta It will be needed if we need to use two async pipes in templateDaloris
I thought it was the case with ngContainer and ngFor directives, read not carefullyDandridge
D
10

UPDATE: shareReplay is needed if you call variable multiple times in template, misunderstand the question.

shareReplay() should be on component level, cause when you call the fetchMyEmployeeData() it is a function that returns new Observables(and new shareReplays for each)

in component create:

employeeData$ = this.myDataService.fetchMyEmployeeData().pipe(shareReplay());

and use in template as

employeeData$ | async

in this case, it would do only a single request for back-end

Dandridge answered 4/12, 2020 at 14:47 Comment(2)
This works even without the shareReplay() operator. But it's not my goal to have it declared in the component. There is not a solution to keep the call in the template?Diapophysis
need to have stackblitz to debug it, hard to say without running it, but as I know it is the way. Also, I have updated the answer, to have @Daloris 's comment be applied. I have misread the question, though of multiple calls in templateDandridge
A
1

Calling service method fetchMyEmployeeData() will always return NEW observable.

  1. When template is rendering service method fetchMyEmployeeData() is called and returned new observable (it's important that it's new).
  2. Async pipe receiving this observable, subscribing on it and when it's finished calls updating of template.
  3. When template updating again everything starts from step 1 in cyclic.

So if you will create a component variable and assign to it result of fetchMyEmployeeData() it will have only 1 unique observable. And subscribing to it via async pipe will not trigger endless http requests.

serverData = myDataService.fetchMyEmployeeData();
<ng-container *ngIf="serverData | async">
</ng-container>
Ache answered 17/9, 2021 at 11:55 Comment(0)
K
0

To elaborate a little bit on why you need initialize it as a property of your component. What was happening is when Angular's change detection runs it sees the expression myDataService.fetchMyEmployeeData() and executes it. This returns you a new observable and makes the request. Change detection keeps on running so it will be called multiple times.

IMHO the template should be as dumb as possible, as in it displays the data you give it and does not get data itself.

Kristiankristiansand answered 4/5, 2021 at 22:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.