AngularJS:将服务注入HTTP拦截器(循环依赖)

我正在尝试为我的AngularJS应用程序编写一个HTTP拦截器来处理身份validation。

这个代码可以工作,但我担心手动注入服务,因为我认为Angular应该自动处理这个:

app.config(['$httpProvider', function ($httpProvider) { $httpProvider.interceptors.push(function ($location, $injector) { return { 'request': function (config) { //injected manually to get around circular dependency problem. var AuthService = $injector.get('AuthService'); console.log(AuthService); console.log('in request interceptor'); if (!AuthService.isAuthenticated() && $location.path != '/login') { console.log('user is not logged in.'); $location.path('/login'); } return config; } }; }) }]); 

我开始做的,但遇到循环依赖问题:

  app.config(function ($provide, $httpProvider) { $provide.factory('HttpInterceptor', function ($q, $location, AuthService) { return { 'request': function (config) { console.log('in request interceptor.'); if (!AuthService.isAuthenticated() && $location.path != '/login') { console.log('user is not logged in.'); $location.path('/login'); } return config; } }; }); $httpProvider.interceptors.push('HttpInterceptor'); }); 

我担心的另一个原因是Angular Docs中的$ http部分似乎显示了一种获取依赖的方法,将“常规方式”注入到Http拦截器中。 在“拦截器”下查看他们的代码片段:

 // register the interceptor as a service $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { return { // optional method 'request': function(config) { // do something on success return config || $q.when(config); }, // optional method 'requestError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }, // optional method 'response': function(response) { // do something on success return response || $q.when(response); }, // optional method 'responseError': function(rejection) { // do something on error if (canRecover(rejection)) { return responseOrNewPromise } return $q.reject(rejection); }; } }); $httpProvider.interceptors.push('myHttpInterceptor'); 

上面的代码应该去哪里?

我想我的问题是这样做的正确方法是什么?

谢谢,我希望我的问题很清楚。

你在$ http和你的AuthService之间有一个循环依赖。

通过使用$injector服务正在做的是通过延迟$ http对AuthService的依赖来解决鸡与蛋问题。

我相信你所做的实际上是最简单的做法。

你也可以这样做:

  • 稍后注册拦截器(在run()块而不是config()块中这样做可能已经成功了)。 但是,你能保证$ http还没有被调用吗?
  • 当你通过调用AuthService.setHttp()或者其他东西来注册拦截器时,手动注入$ http到AuthService。

这是我最终做的

  .config(['$httpProvider', function ($httpProvider) { //enable cors $httpProvider.defaults.useXDomain = true; $httpProvider.interceptors.push(['$location', '$injector', '$q', function ($location, $injector, $q) { return { 'request': function (config) { //injected manually to get around circular dependency problem. var AuthService = $injector.get('Auth'); if (!AuthService.isAuthenticated()) { $location.path('/login'); } else { //add session_id as a bearer token in header of all outgoing HTTP requests. var currentUser = AuthService.getCurrentUser(); if (currentUser !== null) { var sessionId = AuthService.getCurrentUser().sessionId; if (sessionId) { config.headers.Authorization = 'Bearer ' + sessionId; } } } //add headers return config; }, 'responseError': function (rejection) { if (rejection.status === 401) { //injected manually to get around circular dependency problem. var AuthService = $injector.get('Auth'); //if server returns 401 despite user being authenticated on app side, it means session timed out on server if (AuthService.isAuthenticated()) { AuthService.appLogOut(); } $location.path('/login'); return $q.reject(rejection); } } }; }]); }]); 

注意: $injector.get调用应该在拦截器的方法内,如果你试图在别处使用它们,你将继续在JS中得到循环依赖错误。

我认为直接使用$注入是一个反模式。

打破循环依赖的一种方法是使用事件:注入$ rootScope而不是注入$ state。 不要直接redirect

 this.$rootScope.$emit("unauthorized"); 

 angular .module('foo') .run(function($rootScope, $state) { $rootScope.$on('unauthorized', () => { $state.transitionTo('login'); }); }); 

坏的逻辑做出了这样的结果

其实没有一点要求是用户创作或不在Http拦截器。 我会build议将所有的HTTP请求包装成单一的.service(或.factory或.provider),并将其用于所有请求。 在每次调用函数时,都可以检查用户是否login。 如果一切正常,则允许发送请求。

在你的情况下,Angular应用程序将在任何情况下发送请求,你只是在那里检查授权,之后JavaScript将发送请求。

你的问题的核心

$httpProvider实例下调用$httpProvider 。 你的AuthService使用$http$resource ,在这里你有依赖recursion或循环依赖。 如果您从AuthService删除该依赖AuthService ,则不会看到该错误。


另外Herroelen指出,你可以把这个拦截器放在你的模块module.run ,但这更像是一个黑客,而不是一个解决scheme。

如果你要做干净的自我描述的代码,你必须遵循一些固体原则。

在这种情况下,至less“单一职责”原则将对您有所帮助。

如果你只是检查身份validation状态(isAuthorized()),我会build议把这个状态放在一个单独的模块,比如说“身份validation”,它只是保持状态,不使用$ http本身。

 app.config(['$httpProvider', function ($httpProvider) { $httpProvider.interceptors.push(function ($location, Auth) { return { 'request': function (config) { if (!Auth.isAuthenticated() && $location.path != '/login') { console.log('user is not logged in.'); $location.path('/login'); } return config; } } }) }]) 

authentication模块:

 angular .module('app') .factory('Auth', Auth) function Auth() { var $scope = {} $scope.sessionId = localStorage.getItem('sessionId') $scope.authorized = $scope.sessionId !== null //... other auth relevant data $scope.isAuthorized = function() { return $scope.authorized } return $scope } 

(我使用localStorage在这里存储客户端的sessionId,但你也可以在$ http调用之后在你的AuthService中设置这个例子)