我正在使用ES6类构建AngularJS应用程序,并将traceur转换为AMD格式的ES5。
在我的模块中,我导入拦截器类并将其注册为服务,然后在module.config:中使用$httpProvider.interceptors注册此服务
var commonModule = angular.module(moduleName, [constants.name]);
import authenticationInterceptor from './authentication/authentication.interceptor';
commonModule.service('authenticationInterceptor', authenticationInterceptor);
commonModule.config( $httpProvider => {
$httpProvider.interceptors.push('authenticationInterceptor');
});
我的拦截器类注入$q和$窗口服务,并将它们保存在构造函数中以备将来使用。我用调试器完成了这一部分,注入正常进行:
'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;
当我发出一个以401响应的请求时,拦截器会适当地触发,但在"responseError"方法中,"this"变量指向窗口对象,而不是我的拦截器,因此我无法访问this$q或这个$窗口。
我不明白为什么?有什么想法吗?
this
)丢失是因为Angular框架只保留对处理程序函数本身的引用,并在没有任何上下文的情况下直接调用它们。
我最近写了一篇关于使用TypeScript编写$http
拦截器的博客文章,这也适用于ES6类:AngularJS 1.x interceptors using TypeScript。
为了总结我在本文中所讨论的内容,为了不在处理程序中丢失this
,您必须将方法定义为箭头函数,从而在编译的ES5代码中有效地将函数直接放在类的constructor
函数中。
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);
}
}
如果您真的坚持将您的拦截器编写为完全基于原型的类,那么您可以为您的拦截器定义一个基类并对其进行扩展。基类将用实例方法替换原型拦截器函数,因此我们可以这样编写我们的拦截器:
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);
}
}
看看以下几行源代码:
// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
chain.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
chain.push(interceptor.response, interceptor.responseError);
}
});
当interceptor.responseError
方法被推到链中时,它失去了上下文(只推送了函数,没有任何上下文);
稍后,它将被添加到promise作为拒绝回调:
while (chain.length) {
var thenFn = chain.shift();
var rejectFn = chain.shift();
promise = promise.then(thenFn, rejectFn);
}
因此,如果promise被拒绝,rejectFn
(您的responseError
函数)将作为普通函数执行。在这种情况下,如果脚本在非严格模式下执行,则this
引用window
,否则等于null
。
IMHO Angular 1是在考虑ES5的情况下编写的,所以我认为将其与ES6一起使用不是一个好主意。
这与我遇到的问题完全相同,我找到了一个解决方法,在一个自变量中设置"this",就像解决es5上的作用域问题一样,而且效果很好:
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;
要添加到对话中,可以从包含显式绑定类方法的构造函数返回一个对象。
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() {
// ...
}
}
如果你想坚持使用官方的ES6实现,你可以通过在构造函数中定义你的方法来创建实例方法,而不是原型方法。
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);
};
}
}
我喜欢这个解决方案,因为它减少了样板代码的数量;
- 您不再需要将所有依赖项都放在
this
中。因此,不用this.$q
,只需使用$q
即可 - 无需从构造函数返回显式绑定的类方法
有一个额外的缩进级别是不利的。此外,这种方法可能不适合大量实例化的类,因为在这种情况下它会消耗更多的内存。例如。;对于可能在一个页面上多次使用的组件的控制器,使用直接类属性(转换为原型方法)更有效。不要担心服务、提供者和工厂,因为它们都是单独的,它们只会实例化一次。
具有箭头功能的工作解决方案:
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;
为了获得关于箭头函数的其他精细答案,我认为在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);
}
}
用法:
.config(['$provide', '$httpProvider', function ($provide, $httpProvider) {
$provide.factory('reauthenticationInterceptor', AuthenticationInterceptor.create);
$httpProvider.interceptors.push('reauthenticationInterceptor');
}]);
我的工作解决方案不使用ngInject
myInterceptor.js
export default ($q) => {
let response = (res) => {
return res || $q.when(res);
}
let responseError = (rejection) => {
//do your stuff HERE!!
return $q.reject(rejection);
}
return {
response: response,
responseError: responseError
}
}
myAngularApp.js
// angular services
import myInterceptor from 'myInterceptor';
// declare app
const application = angular.module('myApp', [])
.factory('$myInterceptor', myInterceptor)
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('$myInterceptor');
}]);
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);
}
}
}