Wanna add some user-friendliness to your Angular app by caching an HTTP response?

Do it with shareReplay.

It's an RxJS operator that you can use as a cache.

When you use shareReplay, users won't have to wait those extra few millseconds (or seconds) for the application to fetch data from a downstream microservice. That will make them happy.

And you'll be happy because your users are happy.

And I'll be happy because I made you happy.

Let's see how this thing works.

A Use Case

Here's why I currently need shareReplay.

I'm working on a CRM application that integrates with Gmail. I've decided (for now) that when users check their Gmail inboxes, they'll only see the top 100 email messages.

I do that to prevent excessive Gmail API requests. Remember: Google places limits on the number of requests per day.

(Yeah, they're pretty generous limits but lets think big and assume that one day this CRM will be the next Salesforce.)

Some users have thousands of email messages in Gmail and if the app shows them in pages of 20, users could be hitting the API all day as they scroll around.

I don't want that. So I've limited users to viewing only the most recent 100 email messages.

But I get them all at once.

Anyhoo, I said all that to say this: I want to cache those 100 email messages that I initially fetch from the downstream service. That way, when the user leaves the inbox and comes back, the app won't have to make another call to retrieve the messages.

The downside to that strategy, of course, is that if the user waits 30 minutes to revisit the inbox, any new emails received within the last half hour won't show up. But I'll address the "refresh" concept in a separate guide.

For now, let's just go with the "cache" concept.

Instant shareReplay

So what is shareReplay?

It's an RxJS operator. 

But what does it do?

It replays the last emitted value. That means if the Observable has many subscribers, that emitted value will be shared to all of them.

Hence the name: shareReplay.

However, before that Observable can have a last emitted value, it needs a first emitted value. 

So the first time a subscriber subscribes to an Observable that uses shareReplay, the code will function just like there's no replay aspect to it at all. For our purposes, that means it will go to the downstream service and get those 100 emails.

But after that, it won't need to go to the downstream service any more. That's because it has a last value that it can just replay.

So let's take a look at how this thing operates. In this particular case, all the fun happens in a class called EmailService.

At Your Service

First thing's first: make the request a property:

  private emailMessagesRequest$: Observable<Email[]> = null;

Yep. That's an object-level property. Not a method-level variable.

Why?

Well, because if you recreate the Observable every time you make the request for the 100 emails, then the application will make the downstream request every time.

In other words, you'll lose the benefit of shareReplay.

So you want to instantiate that Observable just once. And then subscribe to that instantiated Observable as many times as you like.

Now here's the method that does the hard work:

  fetchInbox(refresh?: boolean): Observable<Email[]> {
    let url = `${baseUrl}/email/inbox`; 
    console.log("Fetch inbox URL is " + url);

    if (!this.emailMessagesRequest$ || refresh) {
      this.emailMessagesRequest$ = this.http.get<Email[]>(url).pipe(
        shareReplay(1)
      );
    }

    return this.emailMessagesRequest$;
  }

That fetchInbox() method accepts a single (optional) parameter: refresh. You can tell it's optional because of the question mark.

I won't get into refreshing here, as I indicated earlier. So that parameter is just a placeholder for future great things.

But moving on... the if statement checks to see if the Observable that you just saw has been instantiated yet. If not (or if the refresh boolean is true) then it creates the Observable.

It does that by using HttpClient to invoke a GET request on the downstream service. 

But then we get to the pipe.  That pipe() function, in case you're unaware of it, is the RxJS means of grouping together all the operators that accept and return Observables.

Here, the code is only using a single operator: shareReplay.

But what's that (1)? That's the buffer size. In this case, it's 1 because the service is just sending the last emitted value back to the calling class.

In case it's not clear: "the last emitted value" here is that collection of 100 emails retrieved from the downstream service. It's an array that's treated like a single object.

Now every time another component calls fetchInbox() on EmailService, it will get back the last emitted value. Unless it's the first component to call the method, in which case the service will make an HTTP call to get the emails.

Also, if the component calls fetchInbox() with the optional refresh parameter set to true, the service will make the HTTP call.

Wrapping It Up

So there's your cache with the aid of RxJS.

Now take what you've learned today and apply to your own business requirements.

And have fun!

Photo by Andrea Piacquadio from Pexels