Re-using RxJS Observables

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

  1. The router automatically subscribes to the Observable returned from the AuthGuard
  2. 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.