Caching is important for performance, especially on bandwidth restricted devices and slow networks. In this article we will discuss how to cache http calls using rxjs observables.

Most developers have a good understanding of caching when working with promises, but how can we translate this to observables?

When interacting with observables we typically set up a subscription on the consumer side and react to values coming through the pipe. With http you generally only expect one value since most http calls are single value calls. However, every time you call subscribe on an http observable, the http call will be made again.

Caching

With the subscribe behavior of http observables in mind there are a few ways to implement caching.

One simple approach is to capture the result of the original http call in a cache variable and not resubscribe to the observable if we have a valid value in the cache variable. This works, but we are bypassing rxjs and have to add a fair amount of boilerplate code to manage state.

Instead we want to leverage rxjs to seamlessly pass us cached values through the stream – without the caller having to know anything about cached vs non cached values.

It turns out we can easily add caching to the observable by adding publishReplay(1) and refCount.

getFriends(){ if(!this._friends){ this._friends = this._http.get('./components/rxjs-caching/friends.json') .map((res:Response) => res.json().friends) .publishReplay(1) .refCount(); } return this._friends; }

publishReplay(1) tells rxjs to cache the most recent value which is perfect for single value http calls. refCount() is used to keep the observable alive for as long as there are subscribers.

I have created a simple demo component and put two instances of it on the page. The component just displays a simple list of friends, but the point here is to demonstrate caching between the two instances.

I have added a live demo here where you can follow along.

The component loads the data by calling the observable we defined above.

loadData(){ this.subscription = this._friendsServce .getFriends() .subscribe(res => this.friends = res, error => console.log(error)); }

As we can see, there is no caching logic built into this call.

Loading

On initial load we render two instances of the component where both instances make the same call during ngOnInit. However, if we follow along in the network tab we will only see one http request. This is proof that our caching is working. We can even make the call again by clicking reload, but we will still get the data from cache.

Clearing Cache

Caching is great, but it's important to provide a way to clear the cache if we expect the data to change. In our simple example this can be done by setting the original observable to null.

clearCache(){ this._friends = null; }

Any subsequent calls will then reinitialize the observable, which will cause the http request to be made again.

According to the documentation refCount will keep the channel active until all subscribers have unsubscribed. However, it appears we can still reconnect even after all subscribers have unsubscribed. We can test this by removing both component instances by clicking the Remove and Reset buttons. When components are toggled off we unsubscribe from the observable, but after clicking reset the connection is open again. It's also worth pointing out that the cache still works after reconnecting.

As always the code is on Github.