How to implement time-based caching in Angular using HTTP Interceptor
How to implement time-based caching in Angular using HTTP Interceptor

Introduction

Implementing time-based caching in Angular using HTTP Interceptor is simple and easy. This post shows step by step process of creating simple caching logic for all outgoing HTTP GET API calls.

How to implement time-based caching in Angular using HTTP Interceptor

Why implement caching?

Angular is used to develop SPA applications. Most of the pages in an application will call external HTTP APIs (GET) to retrieve data in order to display it on the screen. Let's say, a user navigates to the same page multiple times which is normal.

This will lead to calling the same GET API multiple times though the data is not changed. A typical example is to get a list of product categories on a page which won't change so often or even if doesn't change at all.

By implementing caching in the front end or we can say as client-side, we will avoid calling the same API often. This will save some network calls and since data is served from the local cache, the application will be faster and give a rich user experience.

Short video tutorial

What is HTTP Interceptor?

In simple words, HTTP Interceptor in Angular is used to intercept the outgoing HTTP API calls and perform some action. E.g. we can intercept the outgoing call and add or modify the request/response. HTTP Interceptors transform the outgoing request before passing it to the next interceptor in the chain, by calling next.handle(transformedReq).

To know more about HTTP Interceptor, refer here.

Implementation of Caching

First, we will create a service named "HttpCacheService" which does the below actions.

  1. Used to retrieve saved response of a particular URL.
  2. For any new outgoing request, used to save the URL and its response.
  3. Invalidate a particular URL response.
  4. Invalidate the entire cache.
import { Injectable } from '@angular/core';
import { HttpResponse } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class HttpCacheService {

  private requests: any = { };

  constructor() { }

  get(url: string): HttpResponse | undefined {
    return this.requests[url];
  }

  put(url: string, response: HttpResponse): void {
    this.requests[url] = response;
  }

  invalidateUrl(url: string): void {
    this.requests[url] = undefined;
  }

  invalidateCache(): void {
    this.requests = { };
  }

}

Implementing HTTP Interceptor

We will implement an HTTP interceptor named "CacheInterceptor"

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpResponse,
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { HttpCacheService } from '../services/http-cache.service';


@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  constructor(
  private cacheService: HttpCacheService
  ) {}

  intercept(
    req: HttpRequest,
    next: HttpHandler
  ): Observable> {
    

    //check if the outgoing calls are GET and MRDH APIs
    if (req.method === 'GET' ) { 
      // attempt to retrieve a cached response
      const cachedResponse:
        | HttpResponse
        | undefined = this.cacheService.get(req.url);

      // return cached response
      if (cachedResponse) {
        console.log(`Returning a cached response: ${cachedResponse.url}`);
        console.log(cachedResponse);
        return of(cachedResponse);
      }

      // send request to server and add response to cache
      return next.handle(req).pipe(
        tap((event) => {
          if (event instanceof HttpResponse) {
            console.log(`Adding item to cache: ${req.url}`);
            this.cacheService.put(req.url, event);
          }
        })
      );
    }
    else {
        // pass along non-cacheable requests 
        return next.handle(req);
    }
  }
}

This interceptor will intercept all outgoing HTTP API calls. The Cache Interceptor will save the URL and its response detecting upon a GET API call. If we don't find any saved response, we will let the call continue and save the response and its URL in our HttpCacheService.

To make this work we need to register this in the main NgModule provider.

Use this code to place it in the NgModule of the application.

providers: [
    { provide: HTTP_INTERCEPTORS, useClass: CacheInterceptor, multi: true }
  ]
.... other codes ignored

Great, that's it. it's as simple as that.

Wait wait. The title says time-based cache implementation. Right, let's implement that next.

TimerService is used to start the timer when the first response is cached. After which the interceptor checks for a particular amount of time and invalidates the cache.

import { Injectable } from '@angular/core';
import { environment } from 'src/environments/environment';

@Injectable({ providedIn: 'root' })
export class TimerService {
  private isTimerStarted = false;
  public dateNow = new Date();
  public dDay = new Date();
  milliSecondsInASecond = 1000;
  hoursInADay = 24;
  minutesInAnHour = 60;
  SecondsInAMinute = 60;

  public timeDifference: any;

  constructor() {}

  private getTimeDifference() {
    this.timeDifference = this.dDay.getTime() - new Date().getTime();    
  }
  
  startTimer() {
    if (!this.isTimerStarted) {
   
      this.dDay.setMinutes(
        this.dDay.getMinutes() + 30 //+environment.cacheTimeInMinutes // you can put this time in environment file to make it configurable.
      );
      this.getTimeDifference();
      this.isTimerStarted = true;
    }
  }

  resetTimer() {
    this.isTimerStarted = false;
    this.getTimeDifference();
  }

  getRemainingTime(): number {
    this.getTimeDifference();
    return this.timeDifference;
  }
}

Updated CacheInterceptor code which uses time service to track down the timing and invalidate the cache after a particular time.

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpResponse,
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { HttpCacheService } from '../services/http-cache.service';
import { TimerService } from '../services/timer.service';


@Injectable()
export class CacheInterceptor implements HttpInterceptor {
  constructor(
  private cacheService: HttpCacheService,
  private timerService: TimerService
  ) {}

  intercept(
    req: HttpRequest,
    next: HttpHandler
  ): Observable> {
    
    this.timerService.startTimer();
    let remainingTimeInMilliseconds = this.timerService.getRemainingTime();
    
    if (remainingTimeInMilliseconds <= 0) {
      
      this.timerService.resetTimer();
      console.log(
        `Invalidating cache due to cache time limit: ${req.method} ${req.url}`
      );
      this.cacheService.invalidateCache();
    } 


    //check if the outgoing calls are GET and MRDH APIs
    if (req.method === 'GET' ) { 
      // attempt to retrieve a cached response
      const cachedResponse:
        | HttpResponse
        | undefined = this.cacheService.get(req.url);

      // return cached response
      if (cachedResponse) {
        console.log(`Returning a cached response: ${cachedResponse.url}`);
        console.log(cachedResponse);
        return of(cachedResponse);
      }

      // send request to server and add response to cache
      return next.handle(req).pipe(
        tap((event) => {
          if (event instanceof HttpResponse) {
            console.log(`Adding item to cache: ${req.url}`);
            this.cacheService.put(req.url, event);
          }
        })
      );
    }
    else {
        // pass along non-cacheable requests 
        return next.handle(req);
    }
  }
}

CacheInterceptor will monitor all the outgoing calls and start the triggers timer. After 30 minutes of time, the cache service will be ready to invalidate the cache. Interceptor invalidates the cache after 30 minutes of time. This is what will happen when the app initiates the first call.

Are you a beginner? Interested in learning Angular? check out this link

Summary

In this post, I showed you how to implement time-based caching in Angular using HTTP Interceptor. I hope you enjoyed this post and it is useful for you.

One thought on “How to implement time-based caching in Angular using HTTP Interceptor

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights