Injetar $ state (ui-router) no interceptor $ http causa dependência circular

O que estou tentando alcançar

Eu gostaria de fazer a transição para um determinado estado (login) no caso de uma solicitação $ http retornar um erro 401. Portanto, criei um interceptor $ http.

O problema

Quando estou tentando inserir ‘$ state’ no interceptor, fico com uma dependência circular. Por que e como faço para corrigir isso?

Código

//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); 

O conserto

Use o serviço $injector para obter uma referência ao serviço $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); 

A causa

O angular-ui-router injeta o serviço $http como uma dependência em $TemplateFactory que então cria uma referência circular para $http dentro do próprio $httpProvider ao despachar o interceptador.

A mesma exceção de dependência circular seria lançada se você tentar injetar o serviço $http diretamente em um interceptador desse modo.

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

Separação de preocupações

Exceções de dependência circular podem indicar que há uma mistura de interesses em seu aplicativo que pode causar problemas de estabilidade. Se você se deparar com essa exceção, reserve um tempo para examinar sua arquitetura para evitar qualquer dependência que acabe se referindo a si mesma.

@Stephen Friedrich’s answer

Concordo com a resposta abaixo que usar o $injector para obter diretamente uma referência ao serviço desejado não é o ideal e pode ser considerado um anti-padrão.

Emitir um evento é uma solução muito mais elegante e também dissociada.

A questão é uma duplicata do AngularJS: Injetando serviço em um interceptador HTTP (dependência circular)

Estou postando novamente minha resposta desse tópico aqui:

Uma correção melhor

Eu acho que usar o $ injector diretamente é um antipadrão.

Uma maneira de quebrar a dependência circular é usar um evento: em vez de injetar $ state, injete $ rootScope. Em vez de redirect diretamente, faça

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

mais

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

Dessa forma você separou as preocupações:

  1. Detectar uma resposta 401
  2. Redirecionar para login

A solução de Jonathan foi ótima até que tentei salvar o estado atual. No ui-router v0.2.10, o estado atual não parece estar preenchido no carregamento inicial da página no interceptor.

De qualquer forma, resolvi usando o evento $ stateChangeError . O evento $ stateChangeError fornece estados de input e saída , além do erro. É muito bacana.

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