How to integrate the interceptor in angular 9?

Shashikant Devani

May 08, 2020 | 6 min read

What is Interceptor in angular?

Interceptors provide a mechanism to intercept and/or mutate outgoing requests or incoming responses. They are very similar to the concept of middle-ware with a framework like Express, except for the front-end.

Why do we use interceptors?
Interceptors can be really useful for features like caching and logging. Interceptors are a way to do some work for every single HTTP request or response.

  • Add a token or some custom HTTP header for all outgoing HTTP requests
  • Catch HTTP responses to do some custom formatting (i.e. convert CSV to JSON) before handing the data over to your service/component
  • Log all HTTP activity in the console
  • Handle HTTP request by its status code and do common error handle here to manage all HTTP errors.

How we configure interceptor? OR Basic Setup of interceptor.
To implement an interceptor, you’ll want to create a class that’s injectable and that implements HttpInterceptor. The intercept method takes two arguments, req and next, and returns an observable of type HttpEvent.

  • req is the request object itself and is of type HTTP Request.
  • next is the HTTP handler, of type HTTP Handler. The handler has a handle method that returns our desired HttpEvent observable.

First, create a service that implements HttpInterceptor:

import { HttpInterceptor} from '@angular/common/http';
import { Injectable } from '@angular/core';
 
@Injectable()
export class TokenInterceptorService implements HttpInterceptor {
   intercept(req: HttpRequest<any>, next: HttpHandler):   Observable<HttpEvent<any>> {
       // All HTTP requests are going to go through this method
   }
}

It has to be added to the list of all HTTP_INTERCEPTORS, which can be done that way in app.module.ts:

@NgModule({
  ...
  providers: [{
    provide: HTTP_INTERCEPTORS, 
    useClass: TokenInterceptorService, 
    multi: true
  }]
})
export class AppModule { }

How we can use multiple interceptors?
You could define multiple interceptors with something like this

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: MySecondInterceptor, multi: true }],

The interceptors will be called in the order in which they were provided. So with the above, MyInterceptor would handle HTTP requests first.

How to Modifying Requests using Interceptor?
HTTP Request objects are immutable, so in order to modify them, we need to first make a copy, then modify the copy and call handle on the modified copy. The request object’s clone method comes-in handy to do just that.
Here’s a simple interceptor that sets the filter query param to a value of completed

@Injectable()
export class MyInterceptor implements HttpInterceptor {
  intercept( req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (req.body) {
      const duplicate = req.clone({ body: req.body.replace(/pizza/gi, '🍕') });
      return next.handle(duplicate);
    }
    return next.handle(req);
  }
}

Here is some example of where we can use interceptor

  1. Looking for Unauthorised Responses
    When tokens expire we will generally get a 401 Unauthorised response back from the server.
  • This gives us an indication that we need the user to log in again to get a new token.
  • We have some choices to make at this point.
  • Do we want to redirect to a specific route that has a login form?
  • Do we want to show a modal?
  • Do we want to attempt to refresh the token?

We need to set up the interceptor to handle responses. The intercept method returns an observable which means we can capture the success and error channels for a response and operate on them however we like

This is also a great spot to cache any failed requests.

// jwt.interceptor.ts
 
@Injectable()
export class JwtInterceptor implements HttpInterceptor {
  constructor(public auth: AuthService) {}
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    
    return next.handle(request).do((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
        // do stuff with response if you want
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse) {
        if (err.status === 401) {
          // redirect to the login route
          // or show a modal
        }
      }
    });
  }
}
// auth.service.ts
 
import { HttpRequest } from '@angular/common/http';
 
@Injectable()
export class AuthService {
cachedRequests: Array<HttpRequest<any>> = [];
public collectFailedRequest(request): void {
    this.cachedRequests.push(request);
  }
public retryFailedRequests(): void {
    // retry the requests. this method can
    // be called after the token is refreshed
  }
}

2. Manage Authentication
First on the list is authentication! It is just so fundamental for many applications that we have a proper authentication system in place. This is one of the most common use cases for interceptors and for a good reason. It fits right in!

There are several things connected to authentication we can do:

  • Add bearer token
  • Refresh Token
  • Redirect to the login page

We should also have some filtering for when we send the bearer token. If we don’t have a token yet, then we are probably logging in and should not add the token. And if we are doing calls to other domains, then we would also not want to add the token. For example, if we send errors into Slack.
This is also a bit more complex than the other interceptors.
Here is an example of how it can look with some explaining comments

import { Injectable } from "@angular/core";
import { HttpEvent, HttpInterceptor, HttpHandler,HttpRequest, HttpErrorResponse } from "@angular/common/http";
import { throwError, Observable, BehaviorSubject, of } from "rxjs";
import { catchError, filter, take, switchMap } from "rxjs/operators";
 
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private AUTH_HEADER = "Authorization";
  private token = "secrettoken";
  private refreshTokenInProgress = false;
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
 
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    if (!req.headers.has('Content-Type')) {
      req = req.clone({
        headers: req.headers.set('Content-Type', 'application/json')
      });
    }
 
    req = this.addAuthenticationToken(req);
 
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error && error.status === 401) {
         // 401 errors are most likely going to be because we have an expired token that we need to refresh.
          if (this.refreshTokenInProgress) {
            // If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value
            // which means the new token is ready and we can retry the request again
            return this.refreshTokenSubject.pipe(
              filter(result => result !== null),
              take(1),
              switchMap(() => next.handle(this.addAuthenticationToken(req)))
            );
          } else {
            this.refreshTokenInProgress = true;
 
            // Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
            this.refreshTokenSubject.next(null);
            
            return this.refreshAccessToken().pipe(
              switchMap((success: boolean) => {               
                this.refreshTokenSubject.next(success);
                return next.handle(this.addAuthenticationToken(req));
              }),
              // When the call to refreshToken completes we reset the refreshTokenInProgress to false
              // for the next time the token needs to be refreshed
              finalize(() => this.refreshTokenInProgress = false)
            );
          }
        } else {
          return throwError(error);
        }
      })
    );
  }
 
  private refreshAccessToken(): Observable<any> {
    return of("secret token");
  }
 
  private addAuthenticationToken(request: HttpRequest<any>): HttpRequest<any> {
    // If we do not have a token yet then we should not set the header.
    // Here we could first retrieve the token from where we store it.
    if (!this.token) {
      return request;
    }
    // If you are calling an outside domain then do not add the token.
    if (!request.url.match(/www.mydomain.com\//)) {
 return request;
    }
    return request.clone({
      headers: request.headers.set(this.AUTH_HEADER, "Bearer " + this.token)
    });
  }
}

3. Set Headers
We can do a lot by manipulating headers. Some things are:

  • Authentication/authorization
  • Caching behaviour; for example, If-Modified-Since
  • XSRF protection
const modified = req.clone({ 
  setHeaders: { "X-Man": "Wolverine" } 
});
return next.handle(modified);

4. Converting response
When the API returns a format we do not agree with, we can use an interceptor to format it the way we like it.

This could be converting from XML to JSON or like in this example property names from PascalCase to camelCase.
If the back-end doesn’t care about JSON/JS conventions we can use an interceptor to rename all the property names to camelCase.

return next.handle(req).pipe(
  map((event: HttpEvent<any>) => {
    if (event instanceof HttpResponse) {
      let camelCaseObject = mapKeys(event.body, (v, k) => camelCase(k));
      const modEvent = event.clone({ body: camelCaseObject });
      
      return modEvent;
    }
  })
);

Third Rock Techkno is a leading IT services company. We are a top-ranked web, voice and mobile app development company with over 10 years of experience. Client success forms the core of our value system.

We have expertise in the latest technologies including angular, react native, iOs, Android and more. Third Rock Techkno has developed smart, scalable and innovative solutions for clients across a host of industries.

Our team of dedicated developers combine their knowledge and skills to develop and deliver web and mobile apps that boost business and increase output for our clients.