Angular is used to build JavaScript-heavy single-page based web applications. Single page apps or SPA’s load the entire content of a site within a single page. This single page is usually an index.html file. Today, we will talk about Angular HTTP Interceptors in detail.

It allows us to deliver rich, dynamic, and fast-loading content that mimics that of desktop applications. Angular expects the browser to build the page. The server sends the browser all the data to build the page which includes the HTML, CSS, and JavaScripts files.

Angular allows us to write much more succinct code than using plain JavaScript. We can add If conditions, loops, and local variables directly within our templates.

Single Page Applications load the full site content within a single page using an index.html file such that once the page is loaded, we can update a section within that page by clicking on them. Due to the AngularJS framework, we need fewer lines of code.

Overview of Angular HTTP Interceptor?

HttpInterceptor came along with Angular 4.3. It gives us a way to intercept HTTP requests and responses to transform or handle them before passing them along.

As per Angular Docs, “Although Angular HTTP Interceptors are capable of mutating requests and responses, the HttpRequest and HttpResponse instance properties are read-only, rendering them largely immutable.”

This may be due to the fact that we should try to retry a request, in case it is unsuccessful at first.

Angular Docs goes on to say “Angular applies interceptors in the order that you provide them. If you provide interceptors A, then B, then C, requests will flow in A->B->C and responses will flow out C->B->A.

You cannot change the order or remove Angular Interceptors later. If you need to enable and disable an interceptor dynamically, you’ll have to build that capability into the interceptor itself. “

You must keep this in mind when using multiple Angular HTTP Interceptors.

Common Use Cases of Interceptors

Interceptors are methods to do some work for every HTTP request. Here are a few examples of common use cases for interceptors:

  • Add a token or some custom HTTP header for all outgoing HTTP requests
  • Catch HTTP responses to do custom formatting (i.e. convert CSV to JSON) before transferring the data to your service/component
  • Log all HTTP activity in the console

Creating A Basic HTTP Interceptor

 

Creating A Service

To start things off, you must 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
   }
}

To call that interceptor, it has to be added to the list of all HTTP_INTERCEPTORS:

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

Since there could be a varied number of interceptors, we give our interceptor service with “multi:true”

Intercepting a Request

Once we have set-up our interceptor, we need to implement the intercept method to handle a token. An HTTP header is added, If our LoginService has a auth token:

export class TokenInterceptorService implements HttpInterceptor {
      // We inject a LoginService
   constructor(private loginService: LoginService) {}   intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {      console.log('INTERCEPTOR');
      // We retrieve the token, if any
      const token = this.loginService.getAuthToken();
      let newHeaders = req.headers;
      if (token) {
         // If we have a token, we append it to our new headers
         newHeaders = newHeaders.append('authtoken', token);
      }
      // Finally we have to clone our request with our new headers
      // This is required because HttpRequests are immutable
      const authReq = req.clone({headers: newHeaders});      // Then we return an Observable that will run the request
      // or pass it to the next interceptor if any
      return next.handle(authReq);
   }
}

Thus, every time you make an HTTP request, the code will automatically add that header.

Intercepting a Response

This is done through the same intercept method in our Angular HTTP Interceptors. The difference is that the work we want to do with the response has to be registered as a side-effect on the Observable we return from the intercept method.

In simple terms, this means that when we work with an HTTP response, we have to use RxJs operators and the Pipe Method, to simplify the process hire angularjs developer for enhanced results.

return next.handle(authReq).pipe(
// Do custom work here
);

Further to this, we have shown how this can be done by replacing the HTTP response with some fake data for testing purposes only:

return next.handle(authReq).pipe(
// We use the map operator to change the data from the response
map(resp => {
// Several HTTP events go through that Observable
// so we make sure that this is an HTTP response
if (resp instanceof HttpResponse) {
// Just like for request, we create a clone of the response
// and make changes to it, then return that clone
return resp.clone({ body: [{title: 'Replaced data in interceptor'}] });
}
})
);

Ways to Use Interceptors in Angular

Authentication

Having a well-established authentication system in place is essential for Angular Interceptors. It can be used for:

  • Adding bearer tokens
  • Adding refresh tokens
  • Redirecting to the login page

When sending the bearer token, some filtering is required. In case we don’t have a token, then it’s possible that we’re logging in, and do not have a token.

Caching

Caching

It i important to note that there are some interceptors that can handle requests by themselves. They do not need forwarding to next.handle() and we can use them for caching requests.

We do this by using the URL to key in our cache. We can then return an observable if we find a response in the map, by-passing the “next” handler.

You can also improve your performance by doing so, as you don’t have to access the backend when you already have the response cached for Interceptor Angular.

import { Injectable } from '@angular/core';
  import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor, HttpResponse } from '@angular/common/http';
  import { Observable, of } from 'rxjs';
  import { tap, shareReplay } from 'rxjs/operators';
  
  @Injectable()
  export class CacheInterceptor implements HttpInterceptor {
    private cache = new Map<string, any>();
  

      intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      if (request.method !== 'GET') {
        return next.handle(request);
      }
  
        const cachedResponse = this.cache.get(request.url);
      if (cachedResponse) {
        return of(cachedResponse);
      }
  
        return next.handle(request).pipe(
        tap(event => {
          if (event instanceof HttpResponse) {
            this.cache.set(request.url, event);
          }
        })
      );
    }
  }

If you run the request, then click clear response, and then run again, you will use the cache.

Fake Backend

In case you don’t have a backend ready, a fake one can be utilised for development. All you have to do is mock the Response depending on the request, and then return an Observable to HttpResponse.

const body = {
  firstName: "Mock",
  lastName: "Faker"
};return of(new HttpResponse(
  { status: 200, body: body }
));

Profiling

Angular HTTP Interceptors can log an entire HTTP operation due to the fact that they can process the request and respond together.

We can then get the timing of the request and the response and log the outcome with the time taken.

const started = Date.now();
  let ok: string;
  
    return next.handle(req).pipe(
    tap(
      (event: HttpEvent<any>) => ok = event instanceof HttpResponse ? 'succeeded' : '',
      (error: HttpErrorResponse) => ok = "failed"
    ),
    // Log when response observable either completes or errors
    finalize(() => {
      const elapsed = Date.now() - started;
      const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${elapsed} ms.`;
      console.log(msg);
    })
  );

Errors

The use of errors can be implemented in two instances in the Angular HTTP Interceptors. For instance, network interferences are many in mobile situations, and attempting again may finally be victorious. This is important for Interceptor Angular.

Interesting points here are how frequently to retry before surrendering. Also, would it be advisable for us to hold up before retrying or do it right away?

One way is to retry the HTTP call. To get this done, we can use the retry operator from RxJS which can help us re-subscribe to the observable. This will reissue the request.

Otherwise, you can use the status of the exception. What you do, will depend on this.

return next.handle(req).pipe(
    retry(2),
    catchError((error: HttpErrorResponse) => {
      if (error.status !== 401) {
        // 401 handled in auth.interceptor
        this.toastr.error(error.message);      
      }
      return throwError(error);
    })
  );

Before checking the error status in the example below, we retry twice. In the case of no 401 error, we show the error as a popup. All the other errors are then re-thrown for handling.

Notifications

Here, we have a wide range of situations where we could show messages. In the example below, “OBJECT CREATED” is shown each time we get a 201 status over from the server.

return next.handle(req).pipe(
    tap((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse && event.status === 201) {
        this.toastr.success("Object created.");
      }
    })
  );

To check the type of object, we can show “TYPE CREATED”. We can also show more precise messages, by wrapping our data in an object together with a message.

Headers

Here are a few things that can be accomplished by manipulating headers:

  • Authentication/authorization
  • Caching behavior; for example, If-Modified-Since
  • XSRF protection

Here is how you can add a header to the request in the interceptor:

const modified = req.clone({
  setHeaders: { "X-Man": "Wolverine" }
});return next.handle(modified);

Angular uses interceptors for insurance against Cross-Site Request Forgery (XSRF). It does this by perusing the XSRF-TOKEN from a cookie and setting it as the X-XSRF-TOKEN HTTP header.

Since the cookie can be read-only by the code that runs on your domain, the backend can be certain that the HTTP demand originated from your customer application and not an assailant.

Converting

At the point when the API restores an arrangement we don’t concur with, we can utilize an interceptor to design it the manner in which we like it.

This could be changing over from XML to JSON or like in this model property names from PascalCase to camelCase. We can also utilize an interceptor to rename all the property names to camelCase.

Check if there is an npm bundle that can do the truly difficult work for you. In the example below, we are utilizing mapKeys and camelCase from lodash.

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;
      }
    })
  );

Loader

You could set up a loader centrally in an interceptor so that it shows up whenever there are active requests. You can use a “loader service” for this, and make sure that it has a show and hide function.

const loaderService = this.injector.get(LoaderService);
  loaderService.show();
    return next.handle(req).pipe(
    delay(5000),
    finalize(() => loaderService.hide())
  );

We should consider that there could be numerous HTTP calls intercepted. This could be worked through, by having a counter for requests (+1) and responses (- 1).

URL

Below, let’s see how easy or difficult it is to manipulate the URL. Let us take for instance if you want to change HTTP to HTTPS. You can use cloning and replacing, and send the cloned request to the next handler.

// clone request and replace 'http://' with 'https://' at the same time
  const httpsReq = req.clone({
    url: req.url.replace("http://", "https://")
  });
  return next.handle(httpsReq);

Using Angular Interceptors to Manage HTTP Requests

The Angular Interceptor was introduced in version 4.3. It is used to handle HTTP responses and requests.

Prerequisites

Before you begin, however, make sure you have Node.JS installed locally. Check  this link to see how you can do that. You must also be well-versed with Angular CLI to create Angular apps.

Creating the Angular App

You must run the command given below, to create a new Angular app named “Angular-Interceptor” with the CLI.

npx @angular/[email protected] new Angular-Interceptor

Soon, you will see some prompts. Below, is what was selected for this particular instance.

Output

Would you like to add Angular routing? Yes

Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]

Now, you must go to your new project directory and serve the project:

  • cd Angular-Interceptor
  • npx ng serve –open

To view the app, view http://localhost:4200  in your browser. This is now, your basic Angular app.

Styling the Angular App

Once you’ve done the basics, styling will help you make the user experience better.

From the get-go, you will require an Angular Material component. You can find instructions for the same here. Next, you must run the command given below:

npm install –save @angular/[email protected] @angular/[email protected] @angular/[email protected]

The next step will be to add animations. To do this, you must open the src/app/app.module.ts file in your code editor and add the code given below to import BrowserAnimationsModule:

src/app/app.module.ts

...

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({

  ...

  imports: [

    ...

    BrowserAnimationsModule

  ],

  ...

})

export class AppModule { }

Once this is done, add the following code to import the MatDialogModule in your src/app/app.module.ts file:

src/app/app.module.ts

...

import { MatDialogModule } from '@angular/material';

@NgModule({

  ...

  imports: [

    ...

    MatDialogModule

  ],

  ...

})

export class AppModule { }

To make the UI even better, you can add a theme to it. Add indigo-pink.css to your styles.scss file:

src/styles.scss

@import "~@angular/material/prebuilt-themes/indigo-pink.css";

Creating an Angular Interceptor

Creating an Angular Interceptor

Now that the app is stylized, you must create the Angular HTTP Interceptors. To do this, begin with making a new folder for the interceptor, under the app folder.

You can then make a new file – httpconfig.interceptor.ts under the Angular Interceptors folder. Then you must import the dependence given below into this file:

src/app/intreceptor/httpconfig.interceptor.ts

import { Injectable } from '@angular/core';

import {

    HttpInterceptor,

    HttpRequest,

    HttpResponse,

    HttpHandler,

    HttpEvent,

    HttpErrorResponse

} from '@angular/common/http';

import { Observable, throwError } from 'rxjs';

import { map, catchError } from 'rxjs/operators';

Now, you must create a class and implement the interface. For instance,

@Injectable()

export class HttpConfigInterceptor implements HttpInterceptor {

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

      // ...

    }

}

Next, set-up a token, Content-Type, Accept type for an API request. You will find an example below:

const token: string = localStorage.getItem('token');

request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });

request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });

request = request.clone({ headers: request.headers.set('Accept', 'application/json') });

Here is how you can handle the API response:

map((event: HttpEvent<any>) => {

    if (event instanceof HttpResponse) {

        console.log('event--->>>', event);

    }

    return event;

}),

The next move would be to import the httpconfig.interceptor.ts in your AppModule.

src/app/app.module.ts

...

import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';

...

import { HttpConfigInterceptor } from './interceptor/httpconfig.interceptor';

@NgModule({

  ...

  imports: [

    ...

    HttpClientModule

  ],

  ...

})

Once done, add HttpConfigInterceptor to providers.

Creating a Service for Error Handling Presently, you will make the errorDialogService to deal with errors and show the error message for the user.

Make another error dialog under the application folder. Here, you can make your errorDialogService files.

To deal with the error response, make another errordialog.service.ts record and include the code beneath:

src/error-dialog/errordialog.service.ts

import { Injectable } from '@angular/core';

import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

import { ErrorDialogComponent } from './errordialog.component';

@Injectable()

export class ErrorDialogService {

    public isDialogOpen: Boolean = false;

    constructor(public dialog: MatDialog) { }

    openDialog(data): any {

        if (this.isDialogOpen) {

            return false;

        }

        this.isDialogOpen = true;

        const dialogRef = this.dialog.open(ErrorDialogComponent, {

            width: '300px',

            data: data

        }); 

        dialogRef.afterClosed().subscribe(result => {

            console.log('The dialog was closed');

            this.isDialogOpen = false;

            let animal;

            animal = result;

        });

    }

}

Next, we need to display the dialog for the users. To this end, create errordialog.component.ts

src/error-dialog/errordialog.component.ts

import { Component, Inject } from '@angular/core';

import { MAT_DIALOG_DATA } from '@angular/material';

@Component({

  selector: 'app-root',

  templateUrl: './errordialog.component.html'

})

export class ErrorDialogComponent {

  title = 'Angular-Interceptor';

  constructor(@Inject(MAT_DIALOG_DATA) public data: string) {}

}

The next step is to create the errordialog.component.html template

src/error-dialog/errordialog.component.html

<div>

    <div>

        <p>

            Reason: {{data.reason}}

        </p>

        <p>

            Status: {{data.status}}

        </p>

    </div>

</div>

Another visit to httpconfig.interceptor.ts is required to handle the error response. You can begin by importing errordialog.service:

import { ErrorDialogService } from '../error-dialog/errordialog.service';

Add a constructor for errorDialogService:

constructor(public errorDialogService: ErrorDialogService) { }

Use this code handle the error response with catchError and throwError:

catchError((error: HttpErrorResponse) => {

    let data = {};

    data = {

        reason: error && error.error && error.error.reason ? error.error.reason : '',

        status: error.status

    };

    this.errorDialogService.openDialog(data);

    return throwError(error);

})

Now you can import errordialog.service and errordialog.component into the AppModule:

src/app/app.module.ts

...

import { ErrorDialogComponent } from './error-dialog/errordialog.component';

...

import { ErrorDialogService } from './error-dialog/errordialog.service';

...

@NgModule({

  ...

  declarations: [

    ...

    ErrorDialogComponent

  ],

  ...

  providers: [

    ...

    ErrorDialogService

  ],

  entryComponents: [ErrorDialogComponent],

})

Creating a Service File for HTTP Requests

For this, you will need two things – a login API and a Customer Detail API.

You must first create a new “services” folder under the “src” folder. Then, under the “services” folder, make a new login.service.ts file. Add the functions below for the two APIs:

src/services/login.service.ts

import { Injectable } from '@angular/core';

import { HttpClient } from '@angular/common/http'; 

@Injectable()

export class LoginService {

    constructor(private http: HttpClient) { }

    login(data) {

        data = { email: 'admin', password: 'admin' };

        return this.http.post('http://localhost:3070/api/login', data);

    } 

    getCustomerDetails() {

        return this.http.get('http://localhost:3070/customers/details');

    } 

}

Read also: Exploring The 40 Angularjs Dashboard Templates For Front-End Development

Invoking HTTP Client Service

To invoke the HTTP client service in the app component, you must add the two “LoginService” functions to it.  Call the login API with onload and the customers/details with onclick.

src/app/app.component.ts

import { Component } from '@angular/core';

import { LoginService } from './services/login.service';

@Component({

  selector: 'app-root',

  templateUrl: './app.component.html',

  styleUrls: ['./app.component.scss']

})

export class AppComponent {

  title = 'Angular-Interceptor';

  constructor(public loginService: LoginService) {

    this.loginService.login({}).subscribe(data => {

      console.log(data);

    });

  }

  getCustomerDetails() {

    this.loginService.getCustomerDetails().subscribe((data) => {

      console.log('----->>>', data);

    });

  }

}

Remember to include the element for the user to click on in app.component.html:

src/app/app.component.html

...

<h2 (click)="getCustomerDetails()">Get customer details</h2>

Now you can add LoginService to providers in your AppModule:

src/app/app.module.ts

...

import { LoginService } from './services/login.service';

...

@NgModule({

  ...

  providers: [

    ...

    LoginService

  ]

  ...

})
Frequently Asked Questions
  1. What Is Multi True In HTTP Interceptor?

    It is for providing an interceptor the part where there is a statement: Note the multi: true option.

  2. What Is Token Interceptor?

    It ensures that one request per token is processed regularly.

  3. What Is The Difference Between HTTP & HttpClient In Angular?

    The HttpClient is used for performing HTTP requests. It’s an easy alternative to traditional HTTP.