angularjs http interceptor class (ES6) loses binding to 'this'

angularjs, ecmascript-6, gulp-traceur, interceptor, traceur

Solution

The context (`this`) is lost because the Angular framework only keeps references to the handler functions themselves, and invokes them directly without any context, as alexpods has pointed out.

I recently wrote a blog post about writing `$http` interceptors using TypeScript, which also applies to ES6 classes: AngularJS 1.x Interceptors Using TypeScript.

To summarise what I have discussed in this post, in order to not lose `this` in your handlers, you'll have to define the methods as arrow functions, effectively putting the functions directly inside of the class's `constructor` function in the compiled ES5 code.

class AuthenticationInterceptor {

    /* ngInject */
    constructor($q, $window) {
        this.$q = $q;
        this.$window = $window;
    }

    responseError = (rejection) => {
        var authToken = rejection.config.headers.Authorization;
        if (rejection.status === 401 && !authToken) {
            let authentication_url = rejection.data.errors[0].data.authenticationUrl;
            this.$window.location.replace(authentication_url);
            return this.$q.defer(rejection);
        }
        return this.$q.reject(rejections);
    }
}

If you really insist on having your interceptor written as a fully prototype-based class, you could define a base class for your interceptor and extend it. The base class would replace the prototype interceptor functions with instance methods, so we can write our interceptors like this:

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

class AuthenticationInterceptor extends HttpInterceptor {

    /* ngInject */
    constructor($q, $window) {
        super();
        this.$q = $q;
        this.$window = $window;
    }

    responseError(rejection) {
        var authToken = rejection.config.headers.Authorization;
        if (rejection.status === 401 && !authToken) {
            let authentication_url = rejection.data.errors[0].data.authenticationUrl;
            this.$window.location.replace(authentication_url);
            return this.$q.defer(rejection);
        }
        return this.$q.reject(rejections);
    }
}

Problem

I am building and AngularJS app using ES6 classes with traceur transpiling to ES5 in AMD format. in my module I import the interceptor class and register it as a service, and then register this service with the $httpProvider.interceptors in module.config: ``` var commonModule = angular.module(moduleName, [constants.name]); import authenticationInterceptor from './authentication/authentication.interceptor'; commonModule.service('authenticationInterceptor', authenticationInterceptor); commonModule.config( $httpProvider => { $httpProvider.interceptors.push('authenticationInterceptor'); }); ``` My interceptor class injects both $q and the $window services, saves them in the constructor for later use. I followed this part with the debugger and the injection is happening properly: ``` 'use strict'; /*jshint esnext: true */ var authenticationInterceptor = class AuthenticationInterceptor { /* ngInject */ constructor($q, $window) { this.$q = $q; this.$window = $window; } responseError(rejection) { var authToken = rejection.config.headers.Authorization; if (rejection.status === 401 && !authToken) { let authentication_url = rejection.data.errors[0].data.authenticationUrl; this.$window.location.replace(authentication_url); return this.$q.defer(rejection); } return this.$q.reject(rejections); } } authenticationInterceptor.$inject = ['$q', '$window']; export default authenticationInterceptor; ``` When I make a request that responds with a 401 the interceptor triggers appropriately, but in the 'responseError' method the 'this' variable points to the window object and not to my interceptor, hence I do not have access to this.$q or this.$window. I cannot figure out why? Any ideas?

Original source