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

后端 未结 9 1648
轮回少年
轮回少年 2021-02-05 11:14

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

相关标签:
9条回答
  • 2021-02-05 11:43

    Note that using arrow functions in class properties is an experimental feature for ES7. However most transpilers don't have a problem with it.

    If you want to stick to the official ES6 implementation you can create instance methods instead of prototype methods by defining your methods in the constructor.

    class AuthenticationInterceptor {
      /* ngInject */
      constructor($q, $window) {
        
        this.responseError = (rejection) => {
          const authToken = rejection.config.headers.Authorization;
          if (rejection.status === 401 && !authToken) {
            const authentication_url = rejection.data.errors[0].data.authenticationUrl;
            $window.location.replace(authentication_url);
            return $q.defer(rejection);
          }
          return $q.reject(rejection);
        };
        
      }
    }

    I like this solution because it decreases the amount of boilerplate code;

    • You no longer have to put all your dependencies in this. So instead of using this.$q you can just use $q.
    • No need to return explicitly bound class methods from the constructor

    Having one extra level of indentation is a downside. Furthermore this method might not be suitable for classes that are instantiated a lot as it consumes more memory in that case. E.g.; Using direct class properties (transpiled to prototype methods) is more efficient for controllers of components that are likely to be used multiple times on one page. Don't worry about services, providers and factories as these are all singletons and they will only be instantiated once.

    0 讨论(0)
  • 2021-02-05 11:47

    To add to the conversation, you could return an object from the constructor that contains explicitly bound class methods.

    export default class HttpInterceptor {
    
       constructor($q, $injector) {
           this.$q = $q;
           this.$injector = $injector;
    
           return {
               request: this.request.bind(this),
               requestError: this.requestError.bind(this),
               response: this.response.bind(this),
               responseError: this.responseError.bind(this)
           }
       }
    
       request(req) {
           this.otherMethod();
           // ...
       }
    
       requestError(err) {
           // ...
       }
    
       response(res) {
           // ...
       }
    
       responseError(err) {
           // ...
       }
    
       otherMethod() {
           // ...
       }
    
    }
    
    0 讨论(0)
  • 2021-02-05 11:50
    export default class AuthInterceptor{
    
    
        /*@ngInject;*/
        constructor(SomeService,$q){
    
            this.$q=$q;
            this.someSrv = SomeService;
    
    
    
            this.request = (config) =>{
                ...
                this.someSrv.doit();
                return config;
    
            }
    
            this.response = (response)=>{
                ...
                this.someSrv.doit();
                return response;
            }
    
            this.responseError = (response) => {
               ...
               return this.$q.reject(response);
            }
    
    
    
        }
    
    
    
    }
    
    0 讨论(0)
  • 2021-02-05 11:53

    To compelement the other fine answers regarding arrow functions, I think it's a bit cleaner using a static factory method in the Interceptor:

    export default class AuthenticationInterceptor {
     static $inject = ['$q', '$injector', '$rootRouter'];
     constructor ($q, $injector, $rootRouter) {
      this.$q = $q;
      this.$injector = $injector;
      this.$rootRouter = $rootRouter;
     }
    
     static create($q, $injector, $rootRouter) {
      return new AuthenticationInterceptor($q, $injector, $rootRouter);
     }
    
     responseError = (rejection) => {
      const HANDLE_CODES = [401, 403];
    
      if (HANDLE_CODES.includes(rejection.status)) {
       // lazy inject in order to avoid circular dependency for $http
       this.$injector.get('authenticationService').clearPrincipal();
       this.$rootRouter.navigate(['Login']);
      }
      return this.$q.reject(rejection);
     }
    }
    

    Usage:

    .config(['$provide', '$httpProvider', function ($provide, $httpProvider) {
    $provide.factory('reauthenticationInterceptor', AuthenticationInterceptor.create);
    $httpProvider.interceptors.push('reauthenticationInterceptor');
    }]);
    
    0 讨论(0)
  • 2021-02-05 11:55

    This is exactly the same problem I'm experiencing, however, I found a workaround by setting the 'this' in a self variable just like solving the scoping issue on es5, and it works fine:

    let self;
    
    class AuthInterceptor{
    
       constructor(session){
           self = this;
           this.session = session;
       }
    
       request(config){
           if(self.session) {
               config.headers = self.session.getSessionParams().headers; 
           }
           return config;
       }
    
       responseError(rejection){
           if(rejection.status == 401){
    
           }
    
           return rejection;
       }
    
    }
    
    export default AuthInterceptor;
    
    0 讨论(0)
  • 2021-02-05 11:58

    Working solution with arrow functions:

    var AuthInterceptor = ($q, $injector, $log) => {
        'ngInject';
    
        var requestErrorCallback = request => {
            if (request.status === 500) {
              $log.debug('Something went wrong.');
            }
            return $q.reject(request);
        };
    
        var requestCallback = config => {
            const token = localStorage.getItem('jwt');
    
            if (token) {
                config.headers.Authorization = 'Bearer ' + token;
            }
            return config;
        };
    
        var responseErrorCallback = response => {
             // handle the case where the user is not authenticated
            if (response.status === 401 || response.status === 403) {
                // $rootScope.$broadcast('unauthenticated', response);
                $injector.get('$state').go('login');
           }
           return $q.reject(response);
        }
    
      return {
        'request':       requestCallback,
        'response':      config => config,
        'requestError':  requestErrorCallback,
        'responseError': responseErrorCallback,
      };
    };
    
    /***/
    var config = function($httpProvider) {
        $httpProvider.interceptors.push('authInterceptor');
    };
    
    /***/    
    export
    default angular.module('services.auth', [])
        .service('authInterceptor', AuthInterceptor)
        .config(config)
        .name;
    
    0 讨论(0)
提交回复
热议问题