将$ state(ui-router)注入$ http拦截器会导致循环依赖

Nic*_*ise 118 angularjs angular-ui-router

我想要实现的目标

我想转换到某个状态(登录),以防$ http请求返回401错误.因此我创建了一个$ http拦截器.

问题

当我试图在拦截器中插入'$ state'时,我得到一个循环依赖.为什么以及如何解决它?

码

//Inside Config function

    var interceptor = ['$location', '$q', '$state', function($location, $q, $state) {
        function success(response) {
            return response;
        }

        function error(response) {

            if(response.status === 401) {
                $state.transitionTo('public.login');
                return $q.reject(response);
            }
            else {
                return $q.reject(response);
            }
        }

        return function(promise) {
            return promise.then(success, error);
        }
    }];

    $httpProvider.responseInterceptors.push(interceptor);
Run Code Online (Sandbox Code Playgroud)

Jon*_*mbo 212

修复

使用该$injector服务获取对服务的引用$state.

var interceptor = ['$location', '$q', '$injector', function($location, $q, $injector) {
    function success(response) {
        return response;
    }

    function error(response) {

        if(response.status === 401) {
            $injector.get('$state').transitionTo('public.login');
            return $q.reject(response);
        }
        else {
            return $q.reject(response);
        }
    }

    return function(promise) {
        return promise.then(success, error);
    }
}];

$httpProvider.responseInterceptors.push(interceptor);
Run Code Online (Sandbox Code Playgroud)

原因

angular-ui-router将$http服务作为依赖项注入$TemplateFactory其中,然后在调度拦截器时在其$http内部创建循环引用$httpProvider.

如果您尝试将$http服务直接注入拦截器,则会抛出相同的循环依赖性异常.

var interceptor = ['$location', '$q', '$http', function($location, $q, $http) {
Run Code Online (Sandbox Code Playgroud)

关注点分离

循环依赖性异常可以表明应用程序中存在混合的问题,这可能会导致稳定性问题.如果您发现自己遇到此异常,则应花时间查看您的体系结构,以确保避免任何最终引用自身的依赖项.

@Stephen Friedrich的回答

我同意下面的答案,使用$injector直接获得所需服务的参考是不理想的,可以被认为是反模式.

发出一个事件是一个更优雅,也是解耦的解决方案.

  • 用法是一样的,你将`$ injector`服务注入你的拦截器并调用`$ injector.get()`你需要获得`$ state`服务. (3认同)
  • 如果您使用的是Angular的更高版本,`$ httpProvider.responseInterceptors.push`将不再起作用.请使用`$ httpProvider.interceptors.push()`.你也必须修改`拦截器`.无论如何,谢谢你的精彩回答!:) (2认同)

Ste*_*ich 25

问题是AngularJS的重复:将服务注入HTTP拦截器(循环依赖)

我在这里重新发布我的答案:

一个更好的修复

我认为直接使用$ injector是一个反模式.

打破循环依赖的一种方法是使用一个事件:不是注入$ state,而是注入$ rootScope.做,而不是直接重定向

this.$rootScope.$emit("unauthorized");
Run Code Online (Sandbox Code Playgroud)

加

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

这样你就把问题分开了:

  1. 检测401响应
  2. 重定向登录

  • 实际上,这个问题与这个问题是重复的,因为这个问题在之前被问过.喜欢你优雅的修复,所以有一个upvote.;-) (2认同)

Jus*_*bel 16

Jonathan的解决方案很棒,直到我试图保存当前状态.在ui-router v0.2.10中,拦截器中的初始页面加载似乎没有填充当前状态.

无论如何,我通过使用$ stateChangeError事件来解决它.在$ stateChangeError事件使您既要和来自美国,以及错误.这很漂亮.

$rootScope.$on('$stateChangeError',
    function(event, toState, toParams, fromState, fromParams, error){
        console.log('stateChangeError');
        console.log(toState, toParams, fromState, fromParams, error);

        if(error.status == 401){
            console.log("401 detected. Redirecting...");

            authService.deniedState = toState.name;
            $state.go("login");
        }
    });
Run Code Online (Sandbox Code Playgroud)

  • 如果要拦截不触发状态更改的请求,该怎么办? (4认同)