Angular Interceptors are a powerful feature in Angular's HttpClient module, that allows us to process HTTP requests before they are finally sent to a server. This concept of 'middleware' is also used in backend development to do similar things.

This is useful because functionality such as error logging, adding headers or ensuring that requests follow a particular standard can all be done centrally from an interceptor which all requests (or specific requests, such as all authentication requests) are processed through before being sent to the server.

Ultimately, this allows developers to to define such logic once for all requests, instead of in every single HTTP service call. We'll jump straight into some code and uncover how it works.

Show me the code

The following code is taken from the official Angular documentation [1]:

  import { AuthService } from '../auth.service';
  @Injectable()
  export class AuthInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
  // Get the auth token from the service.
  const authToken = this.auth.getAuthorizationToken();

  // Clone the request and replace the original headers with
  // cloned headers, updated with the authorization.
  const authReq = req.clone({
    headers: req.headers.set('Authorization', authToken)
  });

  // send cloned request with header to the next handler.
  return next.handle(authReq);
}

}

What does it do?

This code simply adds an authorisation token to every request that is passed through the interceptor -we will go through how to pass requests through at a later stage.

It makes a copy of the request using the clone method because making the data immutable ensures that even if our requests are re-processed several times (this can happen because the app may retry some method until it succeeds), we can be confident that our request data will not be changed unexpectedly. Consider the following example for clarity, assuming it was not immutable, but mutable:

  1. We increment some header data by 1: authoristion-header-1 to authoristion-header-2
  2. We re-process this request, which causes it to become: authoristion-header-3, now it works, the request was incremented by 1
  3. We actually wanted it to be authoristion-header-1, but because it was mutable, the data changed in a way we didn't want it to

The HttpInterceptor Interface

Inside of the HttpClient module, the HttpInterceptor interface is what is used to implement interceptors. This interface has only on method - intercept(). Below is the method definition in the interface [2]:

interface HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>
}

The HttpRequest and HttpHandler Interfaces

The HttpRequest interface allows us to make a copy of the request, process it and then pass it into the interceptor chain.

Having a chain of interceptors is a robust and scalable design by the Angular team, because regardless of how many interceptors are in our application, they will run as part of a chain, one by one using the next.handle() method on the HttpHandler interface.

This means that we can centrally process the entirety of our project's HTTP requests, which is very powerful and maintainable, especially in large codebases.

Passing our requests through our interceptor/interceptors

Simply provide the interceptor to the module that your HTTP services belong to and it will work. We provide them using Angular's dependency injection system:

    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  

HTTP_INTERCEPTORS is simply a constant that represents all of the interceptors that we have defined. There is more detail on how this works in the documentation: https://angular.io/guide/http#provide-the-interceptor

References:

  • [1] https://angular.io/guide/http#set-default-headers
  • [2] https://angular.io/api/common/http/HttpInterceptor