CouchCoder

Search


Latest Posts


Recent Comments


February 2017
M T W T F S S
« Apr    
 12345
6789101112
13141516171819
20212223242526
2728  
CouchCoder

AngularJS 1.x Interceptors Using TypeScript

When writing TypeScript, we try to think of everything in terms of classes and types, so when it comes to writing a `$http` interceptor, the question is how to express it in terms of a class. In this blog post I'm going to show you how you can define interceptors using TypeScript in AngularJS 1.x.

MerottMerott

When writing TypeScript, we try to think of everything in terms of classes and types, so when it comes to writing a $http interceptor, the question is how to express it in terms of a class. In this blog post I’m going to show you how you can define interceptors using TypeScript or ES6 in AngularJS 1.x.

I’m using TypeScript here, but the ES6 (ES2015) code would be exactly the same – just without the typings, of course!

The DefinitelyTyped Angular definitions define two interfaces relating to HTTP interceptors: IHttpInterceptor and IHttpInterceptorFactory.

We want to intercept successful requests and responses, so let’s go ahead and implement the IHttpInterceptor interface:

class ApiCallInterceptor implements ng.IHttpInterceptor {
  // @ngInject
  static factory($q:ng.IQService):ApiCallInterceptor {
    return new ApiCallInterceptor($q);
  }

  constructor(private $q:ng.IQService) {
  }

  // created as instance method using arrow function (see notes)
  request = (config:ng.IRequestConfig):ng.IRequestConfig => {
    console.info('Request:', config);

    // modify config

    return config;
  };

  // created as instance method using arrow function (see notes)
  response = <T>(
      response: ng.IHttpPromiseCallbackArg<T>
  ):ng.IPromise<T> => {
    console.info('Response:', response);

    // modify response

    return this.$q.when(response);
  };
}

let httpConfig = ($httpProvider:ng.IHttpProvider) => {
  /* push the factory function to the array of $httpProvider
   * interceptors (implements IHttpInterceptorFactory) */
  $httpProvider.interceptors.push(ApiCallInterceptor.factory);
};

angular.module('app').config(httpConfig);

There are two main points to note in the above code:

  1. We’ve defined the request and response handlers as instance methods using arrow functions. They will not be created on the prototype of our ApiCallInterceptor, but rather directly attached to our interceptor from within its constructor function, as evident from the compiled JavaScript code:
        function ApiCallInterceptor($q) {
          var _this = this;
    
          this.$q = $q;
    
          this.request = function(config) {
            console.info('Request:', config);
            // modify config
            return config;
          };
    
          this.response = function(response) {
            console.info('Response:', response);
            // modify response
            return _this.$q.when(response);
          };
        }
      

    We’ve had to do that because the Angular 1.x implementation of interceptors only keeps references to the handler functions themselves, and invokes them directly without any context, which means we’d lose this inside of our request/response handlers.

  2. We’ve created a static factory method on the ApiCallInterceptor class, which conforms to the IHttpInterceptorFactory interface, and uses the class’s constructor to create a new interceptor. The Angular framework invokes this factory function to create a new interceptor.

Alternative Option

If you really insist on having your interceptor written as a fully prototype-based class, an alternative solution to our problem with losing the this binding (note 1), is to define a base class for our interceptors. Our base class will replace the prototype interceptor functions with instance methods, so we can write our interceptor as a real class:

class HttpInterceptor {
  constructor() {
    ['request', 'requestError', 'response', 'responseError']
        .forEach((method) => {
          if(this[method]) {
            this[method] = this[method].bind(this);
          }
        });
  }
}

class ApiCallInterceptor extends HttpInterceptor
                         implements ng.IHttpInterceptor {
  // @ngInject
  static factory($q:ng.IQService):ApiCallInterceptor {
    return new ApiCallInterceptor($q);
  }

  constructor(private $q:ng.IQService) {
    super();
  }

  request(config:ng.IRequestConfig):ng.IRequestConfig {
    console.info('Request:', config);

    // modify config

    return config;
  };

  response<T>(
      response: ng.IHttpPromiseCallbackArg<T>
  ):ng.IPromise<T> {
    console.info('Response:', response);

    // modify response

    return this.$q.when(response);
  };
}

let httpConfig = ($httpProvider:ng.IHttpProvider) => {
  /* push the factory (implements IHttpInterceptorFactory) 
     to the array of $httpProvider interceptors */
  $httpProvider.interceptors.push(ApiCallInterceptor.factory);
};

angular.module('app').config(httpConfig);

I'm a web developer who enjoys coding on the couch as much as I enjoy developing applications as a profession. I owe much of my success as a developer to the very people who blog and share, and this is my attempt to give something back to the community. I'm an advocate of writing clean, concise, well-structured code, and I also love to experiment with new technologies, trying to get my hands dirty with anything and everything.

Comments 9
  • John Walker
    Posted on

    John Walker John Walker

    Reply Author

    Hey, i just wanted to say thank you. I feel the same way, i get so much from people like you and it is really appreciated!


    • Merott
      Posted on

      Merott Merott

      Reply Author

      Hey John, I’m glad that you found this post useful, and thank you for your comment.


  • Jonathas
    Posted on

    Jonathas Jonathas

    Reply Author

    Hello,
    This is a really nice post. This worked for me as long as I didn’t try to use grunt uglify. When I try to uglify, seems like the angular doesn’t know how to inject $q. I tried using “static $inject = [“$q”];” but that didn’t help.
    Would you know how to solve this issue? If found this that mention people with the same issue: https://github.com/angular-ui/bootstrap/issues/2246


    • Jonathas
      Posted on

      Jonathas Jonathas

      Reply Author

      I just the solution for my issues.
      instead of doing this:
      let httpConfig = ($httpProvider:ng.IHttpProvider) => {
      /* push the factory function to the array of $httpProvider
      * interceptors (implements IHttpInterceptorFactory) */
      $httpProvider.interceptors.push(ApiCallInterceptor.factory);
      };
      angular.module(‘app’).config(httpConfig);

      I am now doing this:
      angular.module(“app”).factory(“ApiCallInterceptor”, [“$q”, (q) => new ApiCallInterceptor(q)])
      .config([“$httpProvider”, ($httpProvider: ng.IHttpProvider) => { $httpProvider.interceptors.push(“ApiCallInterceptor”); }]);


      • Merott
        Posted on

        Merott Merott

        Reply Author

        Are you using ng-annotate before applying the uglifier? If you are, then I’d expect the // @ngInject comment above the factory function to automatically take care of that for you.


        • J
          Posted on

          J J

          Reply Author

          If you’re not using ng-annotate, you should be able to add the following to manually annotate the factory:
          (inside of the body of the class, but not inside of a method)
          ApiCallInterceptor.factory.$inject = [“$q”];
          (that’s the code that ng-annotate generates for you


          • J
            Posted on

            J J

            Author

            Whoops! I got that wrong, put my line of code (from above) OUTSIDE of the class. Otherwise, you will get compile time errors


  • Alex
    Posted on

    Alex Alex

    Reply Author

    Hi – thanks for the article. I was not able to get the interceptor to actually intercept requests. I got no errors but also no effect. No breakpoints were hit.


    • Alex
      Posted on

      Alex Alex

      Reply Author

      Nevermind! Sorry for the false alarm! No $http requests were actually being made – it was AJAX via a 3rd party library.