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

Posted

技术标签:

【中文标题】将 $state (ui-router) 注入 $http 拦截器会导致循环依赖【英文标题】:Injecting $state (ui-router) into $http interceptor causes circular dependency 【发布时间】:2013-12-12 09:35:32 【问题描述】:

我正在努力实现的目标

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

【问题讨论】:

【参考方案1】:

修复

使用$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 中,然后在调度拦截器时在 $httpProvider 自身内创建对 $http 的循环引用。

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

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

关注点分离

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

@Stephen Friedrich 的回答

我同意下面的答案,即使用$injector 直接获取对所需服务的引用并不理想,可以被视为反模式。

发出事件是一种更加优雅且解耦的解决方案。

【讨论】:

如何针对 Angular 1.3 进行调整,其中有 4 个不同的函数传递给拦截器? 用法是一样的,你将$injector服务注入你的拦截器并调用$injector.get(),你需要得到$state服务。 我得到 $injectot.get("$state") as >,你能告诉我可能是什么问题吗? 当它是一个简单的情况时,这绝对是一个救命稻草,但是当你遇到这种情况时,这表明你确实在你的代码中的某个地方创建了一个循环依赖,这在 AngularJS 中很容易做到DI。 Misko Hevery 解释得很好in his blog;强烈建议您阅读它以改进您的代码。 如果您使用更高版本的 Angular,$httpProvider.responseInterceptors.push 将不再工作。请改用$httpProvider.interceptors.push()。您还必须修改interceptor。无论如何,感谢您的精彩回答! :)【参考方案2】:

问题与AngularJS: Injecting service into a HTTP interceptor (Circular dependency)重复

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

更好的解决方法

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

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

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

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

这样你就分离了关注点:

    检测 401 响应 重定向到登录

【讨论】:

实际上,这个问题是这个问题的重复,因为这个问题是在那个问题之前提出的。不过,喜欢你优雅的修复,所以请点赞。 ;-) 我不知道该放在哪里。$rootScope.$emit("unauthorized");【参考方案3】:

在我尝试保存当前状态之前,Jonathan 的解决方案非常棒。在ui-router v0.2.10 中,当前状态似乎没有在拦截器的初始页面加载时填充。

无论如何,我通过使用$stateChangeError 事件来解决它。 $stateChangeError 事件为您提供 tofrom 状态以及错误。挺好看的。

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

【讨论】:

我已经为此提交了issue。 我认为这对于 ngResource 是不可能的,因为它总是异步的?我尝试了一些东西,但我没有得到资源调用来防止状态更改...... 如果要拦截一个不触发状态改变的请求怎么办? 您可以使用与上述@Stephen Friedrich 相同的方法,实际上类似于此方法,但使用了自定义事件。

以上是关于将 $state (ui-router) 注入 $http 拦截器会导致循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

angularjs ui-router传值

Angular ui-router 中 $state.transitionTo() 和 $state.go() 之间的区别

AngularJS:无法使用 ui-route $state 获取 html5 模式网址

如何从 ui-router statechange 返回 $state.current.name

ui-router .state参数配置

使用AngularJS中的UI-Router将状态重定向到默认子状态