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


120

O que estou tentando alcançar

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

O problema

Quando estou tentando inserir '$ state' no interceptador, recebo uma dependência circular. Por que e como corrigi-lo?

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

Respostas:


213

O conserto

Use o $injectorserviço para obter uma referência ao $stateserviço.

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 $httpserviço como uma dependência na $TemplateFactoryqual, em seguida, cria uma referência circular $httpdentro de $httpProvidersi ao despachar o interceptador.

A mesma exceção de dependência circular seria lançada se você tentar injetar o $httpserviço diretamente em um interceptador como esse.

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

Separação de preocupações

As exceções de dependência circular podem indicar que há uma mistura de preocupações 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 garantir que você evite quaisquer dependências que acabem se referenciando.

Resposta de @Stephen Friedrich

Eu concordo com a resposta abaixo que usando o $injector para obter diretamente uma referência ao serviço desejado não é o ideal e pode ser considerado um antipadrão.

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


1
Como isso seria ajustado para o Angular 1.3, onde existem 4 funções diferentes transmitidas aos interceptadores?
Maciej Gurban

3
O uso seria o mesmo: você injeta o $injectorserviço no seu interceptador e liga para $injector.get()onde precisa obter o $stateserviço.
Jonathan Palumbo

Recebo $ injectot.get ("$ state") como << não definido >>, você poderia informar qual pode ser o problema?
sandeep kale

Definitivamente, é um salva-vidas quando se trata de uma situação simples, mas quando você se depara com isso, é um sinal de que você realmente criou uma dependência circular em algum lugar do código, o que é fácil de fazer no AngularJS com seu DI. Misko Hevery explica muito bem em seu blog ; recomendo que você o leia para melhorar seu código.
JD Smith #

2
$httpProvider.responseInterceptors.pushnão funcionará mais se você estiver usando versões posteriores do Angular. Use em $httpProvider.interceptors.push()vez disso. Você precisaria modificar o interceptortambém. Enfim, obrigado pela resposta maravilhosa! :)
shyam

25

A pergunta é uma duplicata do AngularJS: injetando serviço em um interceptor HTTP (dependência circular)

Estou repostando minha resposta a partir desse tópico aqui:

Uma correção melhor

Eu acho que usar o injetor $ 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 redirecionar 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 fazer login

2
Na verdade, essa pergunta é uma duplicata dessa pergunta, porque essa pergunta foi feita antes dessa. Ame sua correção elegante, portanto, tenha um voto positivo. ;-)
Aaron Gray

1
Estou confuso sobre onde colocar isso. $ RootScope. $ Emit ("não autorizado");
alex

16

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

Enfim, resolvi usando o evento $ stateChangeError . O $ stateChangeError evento dá-lhe tanto para e de estados, bem como o erro. É bem 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");
        }
    });

Enviei um problema sobre isso.
Justin Wrobel

Eu acho que isso não é possível com ngResource, pois é sempre assíncrono? Eu tentei algumas coisas, mas eu não receber a chamada recurso para evitar uma mudança de estado ...
Bastian

4
E se você deseja interceptar uma solicitação que não aciona uma alteração de estado?
Shikloshi

Você pode usar a mesma abordagem que o @Stephen Friedrich fez acima, que realmente se assemelha a essa, mas usa um evento personalizado.
Saiyancoder 28/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.