RxJS Pt. 3 - Caching Observables

RxJS Pt. 3 - Caching Observables

Caching is the process of retatining retrieved data locally, so future request for that data received faster without reretrieving data from the back-end server. Caching allows us to efficiently reuse previously retrieved data. Cached data can be stored in memory at runtime or external from the application. In this article I will write about runtime store, so the data will be gone when the user exits the application.

Why caching?

Well, firstly to reduce data retrieval loading time on the front-end and improve UI. Secondly to not generate so much requests to the server. If you are familiar with Kubernetes you can know that the price you pay for the service depends on the number of the requests you make on the API. So one reason for the cost and an other for the server load.

Advantages of Caching Data

  • Improves responsiveness - If we cache the retrieved data in the service, the next time the user navigates to the page, the data is available and ready for display and this way it improves performance
  • Reduces bandwidth and network consumption - Specially good for users with bandwith-restricted devices or slow networks
  • Reduces backend server load - Minimizes the number of server requests
  • Reduces computations - If we pipe emitted items through a set of calculations or transformations, we can cache the result, so we do not have to repeat the transformations ``` private cars: Car[] = []

getCars(): Observable { if(this.cars){ return of(this.cars) } return this.http.get(this.url) .pipe( tap(data=>this.cars = data), catchError(this.handleError) ) }

One classic pattern for caching is to define a property to hold the data in the service, then define a method that returns the cache data, or issues a HTTP GET request to get the data. This works but its not declarative

A more declarative approach uses the reactive programming pattern, which I will write about in the next article

private url = 'api/cars'

car$ = this.http.get(this.url) .pipe( shareReplay(1), catchError(this.handleError) )

This caches and shares the resulting Observable. 

# sharePlay
shareReplay() shares its input Observable with other subscribers. On subscription, replays the defined number of emissions. In this example, it replays the last emission. Since data streams only emit once and complete, we normally specify 1 as the argument when using shareReplay with data caching.
shareReplay() is used for
- Sharing Observables
- Caching data in the application
- Replaying emissions to late subscribers

shareReplay is a multicast operator. 
It returns a Subject that shares a single subscription to the underlying source. It takes in an optional buffer size which is the item count of the replay buffer. This is the number of items that are cached and replayed for each subscriber. On subscribe it replays the specified number of emissions. The items stays cached forever, even after there are no more subscribers.

Now it is cool that we cached our data, but when the user uses our application, she/he not just gets data from the server, but creates too. So after she/he created the data, the new data should appear on the website. So how do we update this cached value?

# Updating cached values

private refresh = new BehaviorSubject(undefined)

car$ = this.refresh .pipe( mergeMap(()=> this.http.get(this.url) .pipe( catchError(this.handleError) ) )) ``` We need a trigger for the refresh, and we want it to refresh on subscription, so we define a behaviour subject. We do not care about the emitted value only that the emission occurred. So we define the type argument as void and set a default value to undefined. We do not need to expose the Observable part of this BehaviorSubject because we do not want any code in our application subscribing to this directly. When the refresh BehaviorSubject emits, we use a mergeMap to issue the HTTP request and get the cars.

Did you find this article valuable?

Support Renátó Bogár by becoming a sponsor. Any amount is appreciated!