Angular14 Http Caching using Interceptor

In this article, we are going to discuss Angular HTTP Caching using Interceptor, to implement it we need to have knowledge on Interceptor.

Caching an HTTP request for getting calls is important as it will avoid making API service calls unnecessarily.

An Interceptor can be used to delegate caching without disturbing your existing HTTP calls. Angular documentation already outlines these subjects and can be found here (https://angular.io/guide/http#caching-requests), but it is missing the implementation.

Let’s implement an in-memory Angular HTTP caching mechanism. For this, we need to implement a CacheResolverService and a CacheInterceptor.

//
// cache-resolver.service.ts
//
import { HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
@Injectable()
export class CacheResolverService {
private cache = new Map<string, [Date, HttpResponse<any>]>();
constructor() {}
set(key, value, timeToLive: number | null = null) {
console.log('Set cache key', key);
if (timeToLive) {
const expiresIn = new Date();
expiresIn.setSeconds(expiresIn.getSeconds() + timeToLive);
this.cache.set(key, [expiresIn, value]);
} else {
this.cache.set(key, [null, value]);
}
}
}
//

A CacheResolverService is a dependency injectable service that sets and gets URL query strings along with HttpResponse.

The private cache is instantiated with a Map the class that has a data type of “string” and a tuple of “Date” and HttpResponse Object or collections.

We will store HttpResponse in the map with “key” as a unique identifier.

KeyValue
/API/v1/users?name=jer&username=jer(2) [Sun JUL 30 2022 11:12:11 GMT+0530 (India Standard Time), HttpResponse]
/API/v1/users?name=vin&username=vin(2) [Sun JUL 30 2022 11:12:11 GMT+0530 (India Standard Time), HttpResponse]
Example: Cached using Map with Key as a unique identifier.

Implementing a get cache is fairly easy, Map has a builtin method “Map().get(key)” to fetch the stored values using the identifier key.

//
// cache-resolver.service.ts
//
get(key) {
const tuple = this.cache.get(key);
if(!tuple) return null;
// Extract tuple
const expiresIn = tuple[0];
const httpSavedResponse = tuple[1];
const now = new Date();
// Check if Time To Live has expired
if(expiresIn && expiresIn.getTime() < now.getTime()) {
// Delete if expired
this.cache.delete(key);
return null;
}
return httpSavedResponse;
}
//

The get logic is fairly simple;

  1. Using key as an argument, we are fetching the stored values in the Mapped cache.
  2. If the cache is null then a null is returned to the CacheInterceptor.
  3. Otherwise, the cache is extracted in two parts, 1)  expiresIn the date in seconds and 2) The actually stored HTTP response.
  4. If time to live is expired then delete existing values using the key and return null to CacheInterceptor.
  5. Otherwise, send the cached httpSavedResponse.

CacheInterceptor

import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
HttpResponse,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/internal/Observable';
import { of } from 'rxjs/internal/observable/of';
import { tap } from 'rxjs/internal/operators/tap';
import { CacheResolverService } from '../services/cache-resolver.service';
const TIME_TO_LIVE = 10;
@Injectable()
export class CacheInterceptor implements HttpInterceptor {
constructor(private cacheResolver: CacheResolverService) {}
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// Pass through if it's not GET call
if (req.method !== 'GET') {
return next.handle(req);
}
// Check if latestCheckbox is ticked to get new results
// Catch-then-refresh
if (req.headers.get('x-refresh')) {
return this.sendRequest(req, next);
}
const cachedResponse = this.cacheResolver.get(req.url);
return cachedResponse ? of(cachedResponse) : this.sendRequest(req, next);
}
sendRequest(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
tap((event) => {
if (event instanceof HttpResponse) {
this.cacheResolver.set(req.url, event, TIME_TO_LIVE);
}
})
);
}
}

Firstly, a cache interceptor is just like any other interceptors. Our cache interceptor initializes the cache resolver in the constructor to make use of the caching mechanism.

Next, we need to capture only the GET method calls from the request, and let other methods pass through since functionally we have to cache only the APIs of GET calls alone.

There is a special header validation called “x-refresh”, this header can be set from data service classes to bypass caching mechanisms all together. This could be a feature based on your application requirements.

The last part is where we make use of the CacheResolverService.get(key) method to fetch stored values.

If there are values, then the cached values are projected using the “of” Observable operator. Otherwise, a fresh call is made.

In Conclusion

  In this article we see the HTTP caching technique this caching mechanism is simple, however, if you refresh the browser tab, the stored values will be removed. 

--------------------------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------

Share this

Related Posts

Previous
Next Post »

1 comments:

Write comments