I've been using Angular 2 recently to build a simple but not small application. Angular 2 makes heavy use of RxJS Observables, a library that I'm not yet very familiar with.
When making HTTP calls, the return type is Observable
(Angular 1.x returns a promise). You can easily convert the Observable
to a promise (http.get(url).toPromise()
), but I want to learn this new tool, so I have tried not to.
My application also requires that a user be authenticated before accessing any routes (authentication is done using Active Directory on the backend). See this post for how I achieved this in the router. You will see in that post, that I call the logIn()
method from more than one place, but I obviously don't want more than one request created.
Initially, I thought this would be easy to solve:
private loginRequest : Observable<boolean>;
logIn() : Observable<boolean> {
if (this.loginRequest){
return this.loginRequest;
}
//To enable CORS with auth
let options = new RequestOptions({withCredentials:true});
this.loginRequest = this.http.get((this.serverUrl + '/user/login'), options)
.map(response => this.loggedInUser = response.json())
.map(user => true)
.catch(this.handleError);
return this.loginRequest;
}
Easy enough, I just store the pending Observable
and if logIn()
is called again, I just return the stored version. Except, I was still seeing multiple HTTP requests to my login route.
Around this time, I came across a post from the excellent people at Thoughtram who do amazing work explaining Angular2 concepts.
Turns out, the Http client returns what is called a Cold Observable. Nothing is executed until it is subscribed to. And if it is subscribed to more than once, each subscription triggers a new HTTP request. It's not obvious in my other post where the subscription is taking place (there is no explicit .subscribe()
called).
- The router automatically subscribes to the
Observable
returned from theAuthGuard
- The Aync pipe (
isLoggedIn | async
) also auto-subscribes
I'll let the Thoughtram guys explain the details around Hot vs Cold Observables
, read their post and then come back.
OK, so to fix it, I changed my code to this:
this.loginRequest = this.http.get((this.serverUrl + '/user/login'), options)
.map(response => this.loggedInUser = response.json())
.map(user => true)
// This is the new part
.publishLast()
.refCount()
// ... up to here
.catch(this.handleError);
This now ensures that only the most recent result is returned whenever a new subscription occurs. And because HTTP only ever returns one result, we can be guarenteed that it will always be the same one.