RxJS: Cache data in Angular

10/21/2017 by InstanceMaster

One of the common tasks in building CRUD client application is getting data from the server and displayed to the client in some sort of data presentation: list, form, etc. Usually, retrieved data is stored in the internal memory so it can be presented multiple times without re-retrieving it from the server: cache.

Angular based client is no different in this case. This article is about simple class Cacheable<> that helps managing cache of data retrieved from http server or other any other source. This class can:

  • Trigger “get data” request when no cache available
  • Store retrieved data in the cache
  • Get data to the client as Observable
  • Reset cache

Please see final version with example how to use it on Plunker. To do that: we need Observable, Subject and ReplaySubject from ReactJS:

import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";
import { ReplaySubject } from "rxjs/ReplaySubject";

The logic should trigger data retrieval routine, but actual data retrieving is not a responsibility of this class. So, it must publish a handler which will be called when data should be retrieved:

declare type GetDataHandler<T> = () => Observable<T>;

Basic implementation

Basic implementation is pretty simple:

export class Cacheable<T> {

    protected data: T;
    protected subjectData: Subject<T>;
    protected observableData: Observable<T>;
    public getHandler: GetDataHandler<T>;

    constructor() {
        this.subjectData = new ReplaySubject(1);
        this.observableData = this.subjectData.asObservable();
    }

data – is actual cached data

subjectData – RxJS subject needed for observable

observableData – reference to observable object we are going return to the client

getHadler – reference to get data logic. Typically, it is a logic of handling HTTP GET request

Here is the main part:

    public getData(): Observable<T> {
        if (!this.getHandler) {
            throw new Error("getHandler is not defined");
        }
        if (!this.data) {

If data is not yet assigned the value (no cache yet) we must call handler to perform data revival:

                this.getHandler().map((r: T) => {
                this.data = r;
                return r;
            }).subscribe(
                result => this.subjectData.next(result),
                err => this.subjectData.error(err)
            );
        }

Return observable to the caller:


            return this.observableData;
    }
}

Usage

Declare Cacheable<> object (presumably as part of the service):

    
list: Cacheable<string[]> = new Cacheable<string[]>();

and handler:


this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string[]);
}

Call from a component:


//gets data from server
List.getData().subscribe(…)

Subsequent call or similar call from another component gets data from cache.

Improvements

This article describes very basic usage above. However, it will work when data needs to be retried only once for the service lifetime. What if we need to reset the cache and retrieve data again:


    public resetCache(): void {
        this.data = null;
    }

    public refresh(): void {
        this.resetCache();
        this.getData();
    }

Refresh() will reset the cache and triggers another request to the server. Every subscriber will receive an update.

Please see final version with example how to use it on Plunker.