Angular 2 Http Interceptor

On a recent project upgrading a client's application from AngularJS to Angular (v2.3.0), we needed a clean and robust mechanism to intercept HTTP requests in Angular.

This post will introduce the ng2-http-interceptor module by going over how to configure it, current features, how to use it, and how it works. I will also identify some of its shortcomings and mention an improvement we made to the module to work around one limitation for it to work in our project.

The Problem

If you’ve used AngularJS, you may be familiar with the Http Auth Interceptor Module written by Witold Szczerba (https://github.com/witoldsz/angular-http-auth). This module ties into the AngularJS $http module and broadcasts an event:auth-loginRequired message from $rootScope when an HTTP 401 response is detected. This is a convenient way to redirect the user to the login screen and once the login is confirmed the angular-http-auth module will automatically retry all failed requests transparently so the http request code does not see and have to deal with the HTTP 401 error or perform any retries.

Our client’s application made use of this module so when we upgraded from their code to Angular we needed to find a suitable replacement. There are a few options out there that have been published by the Angular community and the one we chose for our project, which is well written and unit tested, is the ng2-http-interceptor module written by Alex Malkevich (https://github.com/gund/ng2-http-interceptor).

Configuration

To install the ng2-http-interceptor module, run:

$ npm install ng2-http-interceptor --save

This will add ng2-http-interceptor to node_modules and the latest version of the module ("ng2-http-interceptor": "^1.2.5" for example) to package.json “dependencies”.

Now you just need to import the HttpInterceptorModule to your application module. This will override the Http service provider and all requests will be intercepted.

imports: [ BrowserModule, HttpModule, HttpInterceptorModule /*, ...*/ ],

Alternately, you can import HttpInterceptorModule.noOverrideHttp() to keep the original Http service provider and then you must use InterceptableHttp for requests to be intercepted.

Features

The module allows you to do some pretty great things without much effort. The features that really stand out include:

  • Separate interceptors for requests and responses
  • Attach interceptors for specific urls via strings or RegExp’s
  • Modify url, headers, and/or body of requests from request interceptors
  • Cancel requests from request interceptors
  • Choose between overriding the Http service provider so that all requests are intercepted or keep the original and still use interceptors in specific places

Usage

Using the ng2-http-interceptor module is very straight forward. The following example illustrates how to intercept http requests and responses and how to detect an HTTP 401 response.

import { Injectable } from '@angular/core';
import { Response } from '@angular/http';
import { HttpInterceptorService } from 'ng2-http-interceptor';
import { Observable } from 'rxjs';

@Injectable()
export class HttpAuthInterceptorService {
  private numRequests: number = 0;
  private numResponses: number = 0;

  constructor(private httpInterceptor: HttpInterceptorService) {
    this.init();
  }

  private init() {
    this.httpInterceptor.request().addInterceptor(this.requestInterceptor);
    this.httpInterceptor.response().addInterceptor(this.responseInterceptor);
  }

  private requestInterceptor = (data: any[], method: string): any[] => {
    this.numRequests++;
    return data;
  }

  private responseInterceptor = (o: Observable<Response>, method: string): Observable<Response> => {
    this.numResponses++;
    o.subscribe(
      (r: Response) => {},
      (e: Response) => {
        if (e.status === 401) {
          // redirect user to login screen here
        }
      });
    return o;
  }
}

How It Works

Internally, the InterceptableHttpService extends the Http service and overloads all of its functions so that when http calls are made, they arrive in the InterceptableHttpService. To accomplish this, ng2-http-interceptor makes use of Angular’s provider override feature, which allows you to specify the factory used when Http is injected.

{
  provide: Http,
  useFactory: (backend, options, interceptor) =>
    new InterceptableHttpService(backend, options, interceptor),
  deps: [ XHRBackend, RequestOptions, HttpInterceptorService ],
},

With that in place, when a component or service injects Http and makes an http request, the function call arrives in InterceptableHttpService, which is structured as follows (since ng2-http-interceptor uses Proxies to accomplish the extending, the code does not look like the following but this illustrates the general idea).

@Injectable()
export class InterceptableHttpService extends Http {

  request(url: string | Request, options?: RequestOptionsArgs): Observable<any> {
    // intercept request
    // make http call
    // intercept response
    // return observable
  }

  get(url: string, options?: RequestOptionsArgs): Observable<any> {
    // same as request
  }

  post(url: string, body: any, options?: RequestOptionsArgs): Observable<any> {
    // same as request (but handle body)
  }

  put(url: string, body: any, options?: RequestOptionsArgs): Observable<any> {
    // same as request (but handle body)
  }

  delete(url: string, options?: RequestOptionsArgs): Observable<any> {
    // same as request
  }

  patch(url: string, body: any, options?: RequestOptionsArgs): Observable<any> {
    // same as request (but handle body)
  }

  head(url: string, options?: RequestOptionsArgs): Observable<any> {
    // same as request
  }

  options(url: string, options?: RequestOptionsArgs): Observable<any> {
    // same as request
  }
}

Shortcomings

The major limitation we encountered when using the ng2-http-interceptor was that it didn’t work with IE11 due to its reliance on ES6 Proxies. IE11 doesn’t support this ES6 construct and there is no polyfill available for it that works in IE11. Our client required their application to work in IE11 so we had to find a workaround for this issue.

The other shortfall that you might encounter if upgrading an AngularJS application which is using angular-http-auth, is that the ng2-http-interceptor module is not able to automatically retry failed requests as the angular-http-auth did. If your AngularJS application relies on this feature then you’ll have to either restructure your code so that it is not required or implement this feature yourself either in a custom service or by upgrading the ng2-http-interceptor module. In our case, our client’s application did not use this feature so this limitation was not an issue.

The last thing I’d like to point out is the lack of typing on the data object when intercepting a request. The request data comes in as an array (data: any[]) that corresponds to the arguments passed to the http function used. The first element will be the url but the second and third elements will depend on the http function used (see https://angular.io/docs/ts/latest/api/http/index/Http-class.html). This is something that could get improved in the future to make modifying intercepted requests simpler.

Improvement Made

Our client required their application to work in IE11 so to solve this problem we updated the ng2-http-interceptor module and removed the Proxy usage (used to elegantly extend the Http service) and extended the gamut of Http service methods explicitly and cleanly.

Our client required their application to work in IE11 so to solve this problem we updated the ng2-http-interceptor module and removed the Proxy usage (used to elegantly extend the Http service) and extended the gamut of Http service methods explicitly and cleanly.

In the end, I think the change is an overall improvement to the module as the amount of code added to ng2-http-interceptor was less than the amount of code removed as the use of Proxies introduced a lot of boilerplate code in the module and in the unit tests.

In a future post I will go over these changes in more detail but if you are targeting IE11 and interested in seeing these changes in the meantime, feel free to ping me over email or LinkedIn.

Conclusions

The ng2-http-interceptor module provides a clean and robust way to intercept http requests and responses in your Angular application. Requests can be requested, modified, and even cancelled transparently without changing anything in the calling code. These powerful features can be used to redirect users to a login screen on 401 responses or even implement a self-contained loading bar that just works.

The module does have a few shortcomings however, including no way to automatically retry failed requests and not being compatible with IE11 due to its reliance on ES6 Proxies.

If you’re not targeting IE11 then this module in its current state is a great choice for your application from among the available options. If you are targeting IE11 and would still like to leverage the features this module provides, the module can be modified to work with ES6 Proxies.

In a future post, I will go over modifications needed for IE11 and explore in more detail the internals of intercepting http requests and responses in Angular, going into depth about rxjs Observables, which play a central role in the process.