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


120

我正在努力实现的目标

如果$ 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);

Answers:


213

修复

使用$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);

原因

angular-ui-router$http服务作为依赖项注入$TemplateFactory,然后在分派拦截器时在其$http内部创建对$httpProvider自身的循环引用。

如果尝试$http像这样将服务直接注入到拦截器中,则会抛出相同的循环依赖异常。

var interceptor = ['$location', '$q', '$http', function($location, $q, $http) {

关注点分离

循环依赖项异常可能表明您的应用程序中存在混合的关注点,这可能会导致稳定性问题。如果发现自己有这种例外情况,则应该花些时间查看自己的体系结构,以确保避免任何最终导致引用自己的依赖项。

@斯蒂芬·弗里德里希的答案

我同意以下答案,即使$injector用来直接获得所需服务的引用不是理想的选择,可以将其视为反模式。

发出事件是一种更为优雅且脱钩的解决方案。


1
对于Angular 1.3,在拦截器中传递了4个不同的函数的情况下,如何进行调整?
Maciej Gurban 2014年

3
用法是相同的,您将$injector服务注入到拦截器中并$injector.get()在需要获取$state服务的位置进行调用。
乔纳森·帕卢博

我将$ injectot.get(“ $ state”)定义为<<未定义>>,请您说明可能是什么问题?
桑迪普·羽衣甘蓝2015年

在很简单的情况下,这绝对是一个救命稻草,但是当您遇到这种情况时,则表明您确实在代码中的某个地方创建了循环依赖关系,这在AngularJS及其DI中很容易做到。Misko Hevery 在他的博客中很好地解释了这一点;强烈建议您阅读以改进代码。
JD Smith

2
$httpProvider.responseInterceptors.push如果您使用的是Angular的更高版本,则不再起作用。使用$httpProvider.interceptors.push()代替。您还必须修改interceptor。无论如何,感谢您的精彩回答!:)
shyam

25

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

我在这里从该线程重新发布我的答案:

更好的解决方案

我认为直接使用$ injector是一种反模式。

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

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

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

这样,您就可以分离问题:

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

2
实际上,该问题是该问题的重复,因为该问题是在该问题之前被提出的。不过,请爱您的优雅解决方案,因此请多加推荐。;-)
亚伦·格雷

1
我对把它放在哪里感到困惑。$ rootScope。$ emit(“ unauthorized”);
Alex

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");
        }
    });

我已经提交了一个与此有关的问题
贾斯汀·沃贝尔

我认为ngResource不可能总是这样,因为它总是异步的?我尝试了一些方法,但没有得到资源调用以防止状态改变……
Bastian

4
如果要拦截不触发状态更改的请求怎么办?
Shikloshi

您可以使用与@Stephen Friedrich相同的方法,该方法实际上类似于此方法,但是使用自定义事件。
saiyancoder '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.