DEV Community

Mayar Deeb
Mayar Deeb

Posted on

Easy caching with Rxjs

Caching is one of the most important things to learn in any Framework.

Today, I will explain the best and easiest way I have found to cache API response where ever you want (Local storage, memory, cookies, etc. ) using Rxjs.

This way works with React, Vue, Angular, or any other Framework.

1-Create API service

API services is a great way to contain all of your API calls (if your are using Angular I think you already have one).

In Angular you'll have

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class TaskService {
  constructor(private http: HttpClient) {}

all_tasks():Observable<any>
  {
    return this.http.get<any>('example.com/api/tasks');
  }


}
Enter fullscreen mode Exit fullscreen mode

For Any other Framework
you need to install the following libraries if you don't have them already installed.

$ npm install axios rxjs axios-observable
Enter fullscreen mode Exit fullscreen mode

and create your API service like this.

Note: If you don't understand, you can chech
my previous article

import Axios, { AxiosObservable } from "axios-observable";

class TaskService {

    private static _instance: TaskService;

    public static get Instance() {
        return this._instance || (this._instance = new this());
    }

   all_tasks(): AxiosObservable<any> {
            return Axios.get<any>('example.com/api/tasks');
        }

}

export const _TaskService=TaskService.Instance;
Enter fullscreen mode Exit fullscreen mode

2-Do the caching

we have many options to choose for caching, At first I'll choose Localstorage then I'll show you how to store it in memory.

1-import the following operators filter, startWith, tap
from rxjs/operators.

2-add them to your API call using pipe() .
For Angular

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { filter, startWith, tap } from "rxjs/operators";

@Injectable({
  providedIn: 'root',
})
export class TaskService {
  constructor(private http: HttpClient) {}

all_tasks():Observable<any>
  {
    return this.http.get<any>('example.com/api/tasks')
    .pipe(
                tap(res => localStorage['chash_key'] = JSON.stringify(res)),
                startWith(JSON.parse(localStorage['chash_key'] || '{}')),
                filter(res=>Object.keys(res).length !== 0), 
            );
  }


}
Enter fullscreen mode Exit fullscreen mode

For Any other Framework

import Axios, { AxiosObservable } from "axios-observable";
import { filter, startWith, tap } from "rxjs/operators";


class TaskService {

    private static _instance: TaskService;

    public static get Instance() {
        return this._instance || (this._instance = new this());
    }

   all_tasks(): AxiosObservable<any> {
            return Axios.get<any>('example.com/api/tasks')
 .pipe(
                tap(res => localStorage['chash_key'] = JSON.stringify(res)),
                startWith(JSON.parse(localStorage['chash_key'] || '{}')),
                filter(res=>Object.keys(res).length !== 0), 
            );

        }

}

export const _TaskService=TaskService.Instance;
Enter fullscreen mode Exit fullscreen mode

congratulations. that's it 🥳🥳🥳...

The explanation

We use here three operators..

  1. tap
    Used when you want to transparently perform actions or side-effects, such as logging.

  2. startWith
    Used when you want to emit value before any emissions from the source.

  3. filter
    Used when you want to filter emissions from the source.

why do we use them?

we use tap to store the successful API response in Localstorage .

we use startWith to emit the cached value before the emissions arrive from the source, and we add localStorage['chash_key'] || '{}' to emit empty object in case the the cache store is empty.

we use filter to filter the final emissions, so in case the cache store is empty and the startWith operator returns empty object, the filter will block it.

If we don't add filter we might get bugs in the front end.

Note: by the way, if you are getting a raw array from the API like [], you can do startWith(JSON.parse(localStorage['chash_key'] || '[]')) and delete the filter operator.

You can stop here if you want. I'll explain now how to cache in the memory:

To cache in memory you have to do just few changes..

1-declare a private var type any in your class
2-store the API res in that var using tap operator.

your code will be like

For Angular

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { filter, startWith, tap } from "rxjs/operators";

@Injectable({
  providedIn: 'root',
})
export class TaskService {
  constructor(private http: HttpClient) {}

  private TasksCache: any;


all_tasks():Observable<any>
  {
    return this.http.get<any>('example.com/api/tasks')
    .pipe(
                tap(res => this.TasksCache = JSON.stringify(res)),
                startWith(JSON.parse(this.TasksCache || '{}')),
                filter(res=>Object.keys(res).length !== 0), 
            );
  }


}
Enter fullscreen mode Exit fullscreen mode

For Any other Framework

import Axios, { AxiosObservable } from "axios-observable";
import { filter, startWith, tap } from "rxjs/operators";


class TaskService {

    private static _instance: TaskService;
    private TasksCache: any;


    public static get Instance() {
        return this._instance || (this._instance = new this());
    }

   all_tasks(): AxiosObservable<any> {
            return Axios.get<any>('example.com/api/tasks')
 .pipe(
                tap(res => this.TasksCache = JSON.stringify(res)),
                startWith(JSON.parse(this.TasksCache || '{}')),
                filter(res=>Object.keys(res).length !== 0), 
            );

        }

}

export const _TaskService=TaskService.Instance;
Enter fullscreen mode Exit fullscreen mode

The end...

Now, if you try to fetch data for the first time, your successful response will be cached and used for the next fetch you do.

useful links 🔗

https://rxjs.dev/api/operators/tap

https://rxjs.dev/api/operators/filter

https://rxjs.dev/api/operators/startWith

GitHub logo zhaosiyang / axios-observable

Use axios in a rxjs way. use Observable instead of Promise

axios-observable

Observable (as opposed to Promise) based HTTP client for the browser and node.js

Want to use axios in a rxjs (observable) way? There we go!

This API of axios-observable is almost same as API of axios, giving you smooth transition. So the documentation mirrors the one of axios (A few exceptions will be cleared pointed out).

Features

  • Make XMLHttpRequests from the browser
  • Make http requests from node.js
  • Supports the Observable API
  • Intercept request and response
  • Transform request and response data
  • (NEW in v1.1.0) Cancel requests through unsubscribe
  • Automatic transforms for JSON data
  • Client side support for protecting against XSRF

Installing

Using npm: note: axios and rxjs are peer dependencies.

$ npm install axios rxjs axios-observable
Enter fullscreen mode Exit fullscreen mode

Example

Performing a GET request

import Axios from  'axios-observable';
// or const Axios = require('axios-observable').Axios;
// Make a request for a user with a given ID
Axios.get('/user?ID=12345')
  .
Enter fullscreen mode Exit fullscreen mode

.

Top comments (0)