Advanced Caching With RXJS
Caching pronounced as “cashing” is the process of storing the result of a frequently requested operation in a temporary storage area so that the future requests returns faster by referencing the previously stored results.
Caching helps to boost the performance of your Angular applications thus improving the experience of our site, especially when the users are on bandwidth restricted devices or slow networks.
Resources like images, HTML, JS or CSS files are most commonly cached with the standard browser cache or Service Workers But to cache application data we usually use custom mechanisms which generally improves the responsiveness of our application, decreases network costs and makes content available during network interruptions.I highly recommend you to check out the Web caching and cache busting by Amey Desai.
In this post, we will develop an advanced caching mechanism for the Angular application with RxJS and the tools provided by Angular.
These utility functions can be used for:
- Converting existing code for async operations into observables
- Iterating through the values in a stream
- Mapping values to different types
- Filtering streams
- Composing multiple streams
In Angular, HTTP requests like get, post, put or delete are returned as an Observable which are made through the HttpClient that comes with the HttpClientModule. Observables only sends request for the data only when we subscribe to it. If the same observable is subscribed multiple times then it causes the source observable to be created over and over again which is known as the cold observables.
Let’s say in our application we want to navigate from component A to component B where component A displays some categories for the data which are listed out in the component B. While navigating from A to B data will be requested from the cache rather than requesting it from the server everytime and the underlying cached data would update itself every 10 seconds.
Fetching the new data every 10 seconds from the server is not a proper strategy where we would rather use a more sophisticated approach to update the cache via web socket updates. Where the websockets will notify the client if there is any change in existing data so that underlying mechanism can replace the old cached data with new changed one.
For our application we don’t want to automatically update the data in the UI when the cache updates but rather we will wait for the user to enforce the UI update. Because Imagine a situation where a user may be reading data represented on UI and then all of a sudden it’s gone because the data is updated with new data automatically. This would create a bad user experience. Therefore, our users receive notifications whenever new data is available and requires the user to enforce an update which will cause a request to actually update the cache and the UI.
Lets create a service to fetch the data from the server using Angular’s HttpClient
We have declared cache$ as a private property because if we will return the response directly then every subscriber creates a new cache new instance but we want to share a single instance of the cache across all the subscribers so we keep instance in a private property and initialize it as soon as the getter is called the first time. All the consumers who have subscribed will receive the shared instance without creating it every time on subscription.
If we inspect the Component B which is responsible for fetching and rendering data in UI by injecting fetchDataService as injectable.
Component B is initialized with ngOnInit lifecycle hook, where it requests the data by calling the getter function data() exposed by the FetchDataService. When the first time we ask for the data, the cache itself is empty and not yet initialized which is undefined. Internally it calls requestDataFromServer() which gives an Observable that emits the data from the server. SharePlay is a magic operator provided by RxJS which share single subscription to the underlying sequence replaying notifications subject to a maximum time length for the replay buffer. If we will inspect the shareReplay then we will find that it takes bufferSize as its first argument which is used to define the maximum element count of the replay buffer. Once the data comes back from the server it will be cached automatically by the sharePlay operator in observable level and we don’t have to manually store the data in storage that is the magic of the RxJS. When the next time we request the data for the component B, our cache will replay the most recent value and send that to the consumer. There’s no additional HTTP call involved.
You may be wondering where is the code involved in subscribing observable to fetch the data from server, it is actually achieved by using async pipe in HTML template inside ngFor where we loop over the data to display it in UI.
You can check out more on the Async pipe from wonderful blog on Three things you don’t know about async pipe written by Christoph Burgdorf.
Automatic Cache Update
So far we have implemented a simple caching mechanism where most of the work is done by the sharePlay operator which handles the underlying caching and replays the most recent value. In order to update the cache every 10 seconds in the background we will use an operator called timer. Timer emits a sequence a values after a given duration then after each period. Here we will pass the initial delay as first parameter and the regular interval as second parameter to the timer operator.
switchmap is a RxJS operator which unsubscribes from the previously projected Observable and only emit values from the most recently projected Observable.
Here we have set up a timer and for every tick we need to switchMap to an Observable that, on subscription, fetches a new list of data. Abra ka dabra now cache refreshes every 10 seconds that is cool right!!
Send Update Notification
Let’s imagine a situation where the user is currently on component B and reading the represented data on UI, now all of a sudden cache data gets updated after 10 sec which will create a super annoying and bad experience to the end user. Therefore, rather our users should receive notifications when there’s new data available.
We know that we want to update the UI when the user really enforces an update rather than updating cache automatically. To enforce the user we will display an update button on UI together with the notification. To implement update notification, first of all we will create an Observable from DOM events from button clicks. We will bind the click event with the subject and then call next when the event is triggered which causes the specified value to be broadcasted to all Observers that are listening for values.
Subject implements both Observer and Observable and act as a bridge between the template and the view logic. Observables define the data flow and produce the data while Observers can subscribe to Observables and receive the data.
We are using helper method getDataOnce() to map each update event to the latest cached value. Where it uses take(1) internally which will take the first value and then complete the stream. This is crucial because otherwise we would end up with an on-going stream or live connection to the cache. We are calling getDataOnce() initially to show something to the user because otherwise, the screen will be blank until the cache is updated the first time.
For each update which is broadcast the latest cache data is fetched and merged pipe operator is used so that it does not unsubscribe from the previously projected inner Observable and simply merges the inner emissions in the output Observable.
Finally, initial data and latest cache data is merged together.
Display and Hide the Notification
Now comes the final part when to display and hide the update button on the UI. For this first we will create a stream initialNotificaions to listen for all the values emitted by cache but to skip the first because it’s not a refresh. For each initial notification we will map the value to true and if the update button is clicked and notification is produced we will simply map value to false. Depending upon the mapped value now we can show and hide the update button on the UI
Here we have broken this up into multiple streams and have merged them together to turn them into a single Observable to show or hide notifications.
Now you have a way how to use the RxJS library in Angular and its some wonderfull operators which handles network caching in Angular like magic. Which you can implement in your angular project to make your application more responsive and faster.