I recently discovered that our Angular app was making multiple HTTP requests unintentionally to the same REST API endpoint.
The app was using the HttpClient introduced in Angular 4.3, which enables direct access to the JSON response when subscribing to the Observable from the HTTP request. The HTML markup used async pipe bindings : observable$ | async
.
Just here for the code example? Click here
Reproducing the problem
To reproduce this problem, I created a sample app using StackBlitz. This app retrieves my GitHub user information and displays it.
The component:
The HTML:
This seems pretty straightforward. My name and avatar are displayed on the page. Everything is awesome.
But looking at the Chrome DevTools Network tab reveals the issue - 3 (!) HTTP requests have been made:
Needless to say, this will impact the performance, the amount of data transferred and the number of network requests.
Why is this happening
Each async pipe triggers a new HTTP request, because each result$ | async
creates a new subscription/stream to the result$
Observable.
This is not a bug, but the nature of how Observables are implemented to facilitate flexibility.
You can think of the Observable result$
as a function declaration, and that each result$ | async
is calling the same function.
The share operator
The solution is to make the subscribers share the underlying subscription to the Observable.
This can be done using the share operator, which "returns an observable sequence that shares a single subscription to the underlying sequence".
From the RxJS-documentation:
The share operator is a specialization of publish which creates a subscription when the number of observers goes from zero to one, then shares that subscription with all subsequent observers until the number of observers returns to zero, at which point the subscription is disposed.
This means that each result$ | async
will share the same subscription, and only one HTTP request will be made.
Side effects
It's important to notice that the subscription closes when the source Observable completes. This is fine for HTTP requests because you don't need the subscription after you get the HTTP response. This can also be a pitfall, though. Even if using the share-operator, the code below will produce 3 HTTP requests because the markup inside the div is rendered after the first subscription has been closed.
<div *ngIf="result$ | async">
<div>Name: {{ (result$ | async)?.name }}</div><br>
<img [src]="(result$ | async)?.avatar_url" alt="me">
</div>
Cleaner HTML with template variables
For better readability replace the inner async pipes with a template variable:
The above markup will result in only one HTTP request without using the share operator, but it requires that you only access the Observable once - which is really an accident waiting to happen.
The solution
I recommend combining the share operator with template variables, which results in the following implementation for my app:
Note 1:
In a more complex solution you would probably have a separate HTTP service where the share operator is added, so you won't have to remember it each time you create an HTTP request.
Note 2:
Angular 5.0.0/RxJS 5.5 introduced lettable operators, changing the operator import syntax to:import { share } from 'rxjs/operators';
For earlier versions, the share operator must be imported like this: import 'rxjs/add/operator/share'
and used like this: http.get().share()
.
Code example (StackBlitz)
Click EDITOR
to browse the code. Click here for full screen