Flexible Caching and Refreshing with RxJS in Angular

Posted on September 10, 2019

Written by Preston Lamb

angular rxjs

tldr;

A couple weeks ago I wrote about how we can use RxJS to cache data in our services and share that with new subscribers, also making it possible for the data to automatically update when a new subscriber comes in after a certain amount of time. Go read that article here for more information, because this post builds on that one. As I continued on with my project where I was making sure to cache calls as much as possible, I got to a call where we would need to pass a parameter, like an ID, to the API. I still wanted to cache the call if possible, but I didn’t want to create a lot of private variables that I was keeping track of. I just wanted to cache the last item that was asked for. Using a small amount of logic in the service, I was able to do just this.

Caching Data with RxJS

Caching data in RxJS is a handy feature that can increase the responsiveness of your site (or at least it will feel that way to your users). And on some devices and in some scenarios, saving that extra HTTP call is really important. So when data doesn’t change frequently it makes a lot of sense to cache the data that came back and share it in multiple places, or when the user leaves the route and goes back to the same page. To read more about my current favorite way of doing this, read my last article on caching and refreshing data using RxJS here. In that article, you’ll learn how to create an observable that will share the HTTP call data with new subscribers, but after a user defined amount of time, the call will be re-made so that the data is fresh. Here’s what that looks like though, as a refresher for those that have already read that article:

let returnObs$: Observable<any>;

const createReturnObs = (obs: Observable<any>, time: number, bufferReplays: number) =>
	(returnObs$ = obs.pipe(shareReplay(bufferReplays, time)));

export function renewAfterTimer(obs: Observable<any>, time: number, bufferReplays: number = 1) {
	return createReturnObs(obs, time, bufferReplays).pipe(
		first(null, defer(() => createReturnObs(obs, time, bufferReplays))),
		mergeMap(d => (isObservable(d) ? d : of(d))),
	);
}

This function creates an observable that caches the data for the user defined amount of time, and then if a new subscriber comes after it “expires”, the HTTP call is made again and the cached data is replaced.

That function works great, and the new function that I’m going to show you today uses the above, but adds a little bit more flexibility.

Flexibility in Caching Data

The above function is great for data that doesn’t require parameters, like a list of items or something like that. It’s perfect in that case because you can just create the observable and know that every time someone asks for that data it will be the same. But what if you want to get the details of an item and cache it?

In my case, we have some data that needs to be loaded for the current month. Most of the time, they’ll only want the current month, so we want to cache the response. But every now and then they’ll go back to a previous month to view that data. In that case, we want to replace the cache with our new data. What I didn’t want to do, though, was have a big list of observables that I was trying to keep track of, creating a new one each time the function was called. That’s fine, and might work, but I wanted a single observable in my service that was passed around.

Here’s how I did this:

export class SwapiService {
	constructor(private _http: HttpClient) {}

	private lastCharacterQueried: string;
	private steps$: Observable<any>;

	getCharacter(characterId: string) {
		if (characterId !== this.lastCharacterQueried) {
			this.steps$ = renewAfterTimer(this._http.get(`https://swapi.co/api/people/${characterId}`), 10 * 1000);
			this.lastCharacterQueried = characterId;
		}
		return this.steps$;
	}
}

In this example, I’m using the Star Wars API (swapi.co) to get the details of a single character. To do that, I call the getCharacter function and pass an ID into the function. I check to see if the ID is the same as the last one that was passed in. If so, I just return the observable I already created. If now, I create a new observable that will renew after 10 seconds, then save the character ID and return the observable.

Conclusion

If you use this pattern and subscribe to the observable, then create a second subscriber within the time limit, the HTTP call is only made one time. After the 10 seconds, the call is made again. It works perfectly, and allows you to cache the data for as long as you want. It’s flexible, allowing you to provide as many parameters as needed. This same pattern can be extended as well, perhaps by placing the observables into a Map, based on a parameter, and accessing them from the Map if they exist.

And I just have to say, the more I’m learning about RxJS, the more I like it and the more I see its value. If you don’t know much about RxJS, I encourage you to take a few minutes and start to dig in. There are a lot of resources out there, but Ben Lesh, Tracy Lee, and Aaron Frost are three of my favorite Twitter follows that frequently talk/teach/tweet about RxJS.

Click here to subscribe to the newsletter and be the notified when a new blog post is available!