In this article we are going to learn about 3 topics in angular / typescript

  1. Simple Caching
  2. Http Interceptor
  3. Custom Decorators

Caching is more often a good technique to improve application performance. We are going to cache Http response for performance optimization using angular HTTP Interceptors.

Simple Caching Service

Let’s created a simple angular service, with put() method to store response in Map and get() method to retrieve response from Map.

I am using request urlWithParams as key for storing in Map

import { Injectable } from '@angular/core';
import { HttpRequest, HttpResponse } from '@angular/common/http';

@Injectable()
export class RequestCacheService {

    cache = new Map();

    constructor() { }

    get(req: HttpRequest<any>): HttpResponse<any> | undefined {
        const url = req.urlWithParams;
        const cachedResponse = this.cache.get(url);
        if (cachedResponse) {
            return cachedResponse;
        }
        return undefined;
    }

    put(req: HttpRequest<any>, res: HttpResponse<any>): void {
        const url = req.urlWithParams;
        this.cache.set(url, res);
    }
}

Next we can intercept all http request and consume RequestCache service to store / retreive http response based on the url

Before we deep dive in implementing HTTP Interceptor, I will give basic idea about Interceptor.

What is HTTP Interceptor?

HTTP Interceptor are like middle man between the request originated from application to on the way to server and vice versa. This middle man can take read request (or) response object and manipulate / transform request (or) response before it reaches.

To create HTTP Interceptor in angular, there are couple of things we need to do. First we have create a service class and implement HttpInterceptor from '@angular/common/http' package. Second we have to add it in providers.

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

@Injectable()
export class CachingInterceptor implements HttpInterceptor {

    constructor(private requestCache: RequestCacheService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const cachedResponse = this.requestCache.get(req);
        return cachedResponse ? Observable.of(cachedResponse) : this.cacheResponse(req, next);
    }

    cacheResponse(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req).pipe(
            tap(response => {
                if (response instanceof HttpResponse) {
                    this.requestCache.put(req, response);
                }
            })
        );
    }
}

CachingInterceptor class we created here is like an another service, its managed by Angular Dependency Injection system. We must provide the interceptor class before the app use it.

@NgModule({
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: CachingInterceptor, multi: true }
            ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Http Interceptor will intercept all HTTP Request, it’s not gonna be ideal choice to cache all response. It’s often good approach to cache only the response is not quite changing often.

Method #1 – How to cache only limited url(s)?

One of simple and straightforward approach is to add an if condition in intercept method. Let’s take a scenario, when user logged in successfully we wanted to get user preferences from the server and cache only this response. Let’s assume our rest api url to get user preferences is '/api/user/setting?id=123'.

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (req.url.indexOf('/api/user/setting') !== -1) {
            const cachedResponse = this.requestCache.get(req);
            return cachedResponse ? Observable.of(cachedResponse) : this.cacheResponse(req, next);
        }
        return next.handle(req);
    }

In this approach, we have to alter the if condition every time when we have new requirement.

What if we wanted to add few more urls for caching?

What if we wanted to ignore caching all put / post request operation?

Method #2 – How to cache only limited url with @MatchUrl decorator?

Using decorator approach we can separate condition(s) and business logic. Here business logic is to cache response and we can add conditions in decorators.

Let’s create a decorator and name function as MatchUrl and file name as 'match-url-decorator.ts'

import { HttpHandler, HttpRequest } from '@angular/common/http';

export function MatchUrl(props) {
    return function(target: Object, key: string | symbol, descriptor: PropertyDescriptor) {
        const originalMethod: Function = descriptor.value;
        descriptor.value = function(req: HttpRequest<any>, next: HttpHandler) {
            if (req.url.indexOf(props) !== -1) {
                return originalMethod.apply(this, arguments);
            }
            return next.handle(req);
        };
        return descriptor;
    };
}

MatchUrl function take an argument and we can take decision based on the argument in our function return new implementation.

If request url is matched then call original implementation ( i.e caching response ) otherwise pass it to next request handler.

Let’s rewrite CachingInterceptor with @MatchUrl decorator

import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
import { tap } from 'rxjs/operators';
import { RequestCacheService } from './request-cache.service';
import { MatchUrl } from './match-url-decorator';

@Injectable()
export class CachingInterceptor implements HttpInterceptor {

    constructor(private requestCache: RequestCacheService) { }

    @MatchUrl('/api/user/setting')
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        const cachedResponse = this.requestCache.get(req);
        return cachedResponse ? Observable.of(cachedResponse) : this.cacheResponse(req, next);

    }

    cacheResponse(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req).pipe(
            tap(response => {
                if (response instanceof HttpResponse) {
                    this.requestCache.put(req, response);
                }
            })
        );
    }
}

Here is the final implementation of Http Caching with @MatchUrl decorators, similarly we can add multiple decorators to meet future requirements without altering intercept method.


Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.