Manage HTTP Requests In Angular: Http Interceptor

Introduction

Angular framework comes with many features and capabilities. One of key features provided by Angular is Interceptors. In this article, I will talk about the interceptor. There are many scenarios where you might need to change HTTP request or response globally. For example, set user token into the request header or handle HTTP response error. Such scenarios, the HTTP Interceptor is very useful. 

What Are Interceptors?

The interceptor allows you to intercept the outgoing and incoming HTTP request handle using HttpClient service. It is capable to change HTTP header and body when the request goes out and modify the response and handle the HTTP error when the response comes in.

Angular Interceptors

Above diagram shows a typical workflow in Angular app and position of interceptor when performing HTTP service related task. The HTTP interceptor will always be in the middle of any single HTTP request. It will intercept all requests performed by the app. You can set custom header, modify the body when actually request goes out. Similar process happens when the server or Web API reply to the request. It allows you to perform a series of operations before the application consumes the final response. 

How To Create An Interceptor?

Angular CLI provides a command to create interceptor. Using the following command you can create the interceptor service class. Here, I am assuming that you already created Angular project.

//syntax
ng generate interceptor <<Name of the Class>>

//Example
ng generate interceptor httpClient

You can see here, an interceptor service is created and it implements HttpInterceptor interface. To modify the request/response, you need to write your custom code inside the intercept method. This method receives the HTTP request sent by your application and executes the chain of calls. When there is no custom code for transformations then you need to pass request through next.handle(request). This need to repeat for all subsequent interceptor configuration. The above command generate following empty class.

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class HttpClientInterceptor implements HttpInterceptor {

  constructor() { }

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request);
  }
}

Basic Authentication Example

The most common scenario to use the HTTP interceptor is to add token to the authorization header. In the following example, I have put hard-coded token that pass to each request and addAuthenticationToken method is responsible to add token to each request. 

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class HttpClientInterceptor implements HttpInterceptor {

  token: string = "5dadbb1d-03fc-11ed-baea-7640edfdd6b4";
  constructor() { }

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(this.addAuthenticationToken(request));
  }

  addAuthenticationToken(request: HttpRequest<any>) {
    return request.clone({
      setHeaders: {
        Authorization: `Basic ${this.token}`
      }
    })
  }
}

Interceptor must be registered in "app.module.ts" file as a provider to intercept the HTTP request.

//app.module.ts

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { HttpClientInterceptor } from './http-client.interceptor';


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS,  useClass: HttpClientInterceptor, multi: true }
  bootstrap: [AppComponent]
})
export class AppModule { }

To verify that the interceptor is actually modifying the request, I have made some dummy call to the API and found that interceptor adds the authentication token to this request. 

testHttpRequest() {
    this.http.get<any>("http://localhost:1880/InterceptorTest").subscribe((data: any) => {
  });
}

Angular Interceptors

Multiple interceptors - Can we define them?

Yes, you can define multiple interceptors in the application and they all are dealing with their own scope of action. For example, you can define interceptor that is responsible only for set authentication header, another for error handling, etc. Angular will applied the interceptors in the order defined in "Providers" section in app.module.ts file. The Angular has default interceptor "HttpBackend" that always executes last. 

Angular Interceptors

You cannot change the order or remove interceptors on the fly or dynamically. If you want to do so then you have to write smart code that handle this situation.

Some common use-cases for HTTP interceptor

1. Set the default header

The application uses HTTP interceptor many times to set up default header for each request. It is most common scenario to use HTTP interceptor. I have already covered this topic in the previous example. 

2. Handle response HTTP error

The interceptor can work with both request and response so you can write common code to handle the HTTP error that you got as response. For example, if your API return status code 401 then you can redirect to the login component. The tap operator of RxJS can able to capture whether the request is successfully completed or failed. In the following example, if the API returns the 401 and 404 HTTP error then the code shows the alert box.

//http-response.interceptor.ts

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';

@Injectable()
export class HttpResponseInterceptor implements HttpInterceptor {

  token: string = "5dadbb1d-03fc-11ed-baea-7640edfdd6b4";
  constructor() { }

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      tap({
        next: (event) => {
          if (event instanceof HttpResponse) {
            if(event.status == 401) {
              alert('Unauthorize access!!!')
            }
          }
          return event;
        },
        error: (error) => {
          if(error.status == 401) {
            alert('Unauthorize access!!!')
          }
          if(error.status == 404) {
            alert('Page Not Found!!!')
          }
        }
        
      }
      )
    );
  }
}

3. Modify the request data before it sending out

You can be able to access request/response data inside the interceptors so you can be able to modify them before they actually send it to the Web API or server. In the following example, interceptor will change the request body if it is httpput request.

//http-client.interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class HttpClientInterceptor implements HttpInterceptor {

  token: string = "5dadbb1d-03fc-11ed-baea-7640edfdd6b4";
  constructor() { }

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    // Modify the body
    if (request.method == "PUT") {
      return next.handle(this.addAuthenticationTokenAndModifyData(request));
    }
    return next.handle(this.addAuthenticationToken(request));
  }

  addAuthenticationToken(request: HttpRequest<any>) {
    return request.clone({
      setHeaders: {
        Authorization: `Basic ${this.token}`
      }
    })
  }
  addAuthenticationTokenAndModifyData(request: HttpRequest<any>) {
    return request.clone({
      setHeaders: {
        Authorization: `Basic ${this.token}`
      },
      body: { "data" : "Modified"}
    })
  }
}

4. Caching the Request

Every request is passed through the interceptor hence you can also use the interceptor to cache the response of any request. In the following example, all GET requests are cached into the local storage and return it from the cache if it is exist in cache. 

// caching.interceptor.ts

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  HttpResponse
} from '@angular/common/http';
import { Observable, of, tap } from 'rxjs';

@Injectable()
export class CachingInterceptor implements HttpInterceptor {

  constructor() { }

  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    if (request.method == "GET") {
      const cachedResponse: HttpResponse<any> | undefined = this.loadResponseFromCache(request.url);
      // return cached response
      if (cachedResponse) {
        console.log(`cached response: ${cachedResponse.url}`);
        console.log(cachedResponse);
        return of(new HttpResponse({ "body": cachedResponse }));
      }
    }
    return next.handle(request).pipe(
      tap((event) => {
        if (event instanceof HttpResponse) {
          this.saveResponseToCache(request.url, event.clone());
        }
      })
    );;
  }

  saveResponseToCache(key:string, record:any) {
    localStorage.setItem(key, JSON.stringify(record))
  }

  loadResponseFromCache(key: string) {
    const item = localStorage.getItem(key)
    if (item !== null) {
      return JSON.parse(item);
    }
    return null
  }

}

Summary

This article explained about the interceptor in Angular. Using the interceptor, you can handle outgoing request and incoming response at the same place. Here, I have also defined some common use-cases in which you can use interceptor.


Similar Articles