RRGT19
RRGT19

Reputation: 1677

Using async pipe in template causes multiple requests in Angular 11

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.

Upvotes: 13

Views: 5867

Answers (3)

Sergey
Sergey

Reputation: 36

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>

Upvotes: 1

Flechto
Flechto

Reputation: 35

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.

Upvotes: 0

andriishupta
andriishupta

Reputation: 577

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

Upvotes: 11

Related Questions