L'iniezione di $ state (ui-router) in $ http interceptor causa una dipendenza circolare


120

Quello che sto cercando di ottenere

Vorrei passare a un determinato stato (login) nel caso in cui una richiesta $ http restituisca un errore 401. Ho quindi creato un intercettore $ http.

Il problema

Quando provo a inserire '$ state' nell'interceptor ottengo una dipendenza circolare. Perché e come lo risolvo?

Codice

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

Risposte:


213

La correzione

Utilizza il $injectorservizio per ottenere un riferimento al $stateservizio.

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

La causa

angular-ui-router inietta il $httpservizio come una dipendenza in $TemplateFactorycui poi crea un riferimento circolare $httpall'interno dello $httpProviderstesso dopo l'invio dell'interceptor.

La stessa eccezione di dipendenza circolare verrebbe generata se si tenta di iniettare il $httpservizio direttamente in un intercettore in questo modo.

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

Separazione degli interessi

Le eccezioni di dipendenza circolare possono indicare che esiste un misto di preoccupazioni all'interno dell'applicazione che potrebbe causare problemi di stabilità. Se ti trovi con questa eccezione, dovresti dedicare del tempo a esaminare la tua architettura per assicurarti di evitare eventuali dipendenze che finiscono per fare riferimento a se stesse.

Risposta di @Stephen Friedrich

Sono d'accordo con la risposta di seguito che utilizza il $injector per ottenere direttamente un riferimento al servizio desiderato non è l'ideale e potrebbe essere considerato un anti pattern.

Emettere un evento è una soluzione molto più elegante e anche disaccoppiata.


1
Come verrebbe regolato per Angular 1.3, dove ci sono 4 diverse funzioni passate agli intercettori?
Maciej Gurban

3
L'utilizzo sarebbe lo stesso, inietti il $injectorservizio nel tuo intercettore e chiami $injector.get()dove hai bisogno per ottenere il $stateservizio.
Jonathan Palumbo

Ottengo $ injectot.get ("$ state") come << non definito >>, potresti per favore dire quale potrebbe essere il problema?
sandeep kale

Questo è sicuramente un salvavita quando è una situazione semplice, ma quando ti imbatti in questo è un segno che hai effettivamente creato una dipendenza circolare da qualche parte nel tuo codice, che è facile da fare in AngularJS con il suo DI. Misko Hevery lo spiega molto bene nel suo blog ; consiglio vivamente di leggerlo per migliorare il tuo codice.
JD Smith

2
$httpProvider.responseInterceptors.pushnon funziona più se stai usando versioni successive di Angular. Usa $httpProvider.interceptors.push()invece. Dovresti modificare interceptoranche il file. Comunque, grazie per la bellissima risposta! :)
shyam

25

La domanda è un duplicato di AngularJS: iniezione del servizio in un intercettore HTTP (dipendenza circolare)

Sto ripubblicando la mia risposta da quel thread qui:

Una soluzione migliore

Penso che l'uso diretto di $ injector sia un antipattern.

Un modo per interrompere la dipendenza circolare è utilizzare un evento: invece di iniettare $ state, iniettare $ rootScope. Invece di reindirizzare direttamente, fallo

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

più

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

In questo modo hai separato le preoccupazioni:

  1. Rileva una risposta 401
  2. Reindirizza al login

2
In realtà, quella domanda è un duplicato di questa domanda, perché questa domanda è stata posta prima di quella. Adoro la tua soluzione elegante, quindi dai un voto positivo. ;-)
Aaron Gray

1
Non riesco a capire dove metterlo. $ RootScope. $ Emit ("non autorizzato");
alex

16

La soluzione di Jonathan è stata ottima finché non ho provato a salvare lo stato attuale. In ui-router v0.2.10 lo stato corrente non sembra essere popolato al caricamento iniziale della pagina nell'interceptor.

Ad ogni modo, l'ho risolto utilizzando invece l'evento $ stateChangeError . L' evento $ stateChangeError fornisce sia gli stati a che da , oltre all'errore. È piuttosto elegante.

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

Ho presentato un problema su questo.
Justin Wrobel

Penso che questo non sia possibile con ngResource poiché è sempre asincrono? Ho provato alcune cose ma non ricevo la chiamata alla risorsa per impedire un cambio di stato ...
Bastian

4
E se volessi intercettare una richiesta che non attiva un cambio di stato?
Shikloshi

Puoi utilizzare lo stesso approccio di @Stephen Friedrich in precedenza, che in realtà assomiglia a questo ma utilizza un evento personalizzato.
saiyancoder
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.