Das Injizieren von $ state (UI-Router) in den $ http-Interceptor führt zu einer zirkulären Abhängigkeit

120

Was ich versuche zu erreichen

Ich möchte in einen bestimmten Status (Login) wechseln, falls eine $ http-Anfrage einen 401-Fehler zurückgibt. Ich habe daher einen $ http Interceptor erstellt.

Das Problem

Wenn ich versuche, '$ state' in den Interceptor einzufügen, erhalte ich eine zirkuläre Abhängigkeit. Warum und wie behebe ich das?

Code

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

Antworten:

213

Die Reparatur

Verwenden Sie den $injectorDienst, um einen Verweis auf den $stateDienst zu erhalten.

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

Die Ursache

Winkel-ui-Router spritzt den $httpDienst als eine Abhängigkeit in $TemplateFactorydie dann an einer kreisförmigen Referenz erzeugt $httpinnerhalb des $httpProvidernach Versendung der Interceptor selbst.

Dieselbe zirkuläre Abhängigkeitsausnahme wird ausgelöst, wenn Sie versuchen, den $httpDienst wie folgt direkt in einen Interceptor zu injizieren .

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

Trennung von Bedenken

Ausnahmen bei zirkulären Abhängigkeiten können darauf hinweisen, dass in Ihrer Anwendung Bedenken gemischt sind, die zu Stabilitätsproblemen führen können. Wenn Sie mit dieser Ausnahme konfrontiert sind, sollten Sie sich die Zeit nehmen, Ihre Architektur zu überprüfen, um sicherzustellen, dass Abhängigkeiten vermieden werden, die letztendlich auf sich selbst verweisen.

@ Stephen Friedrichs Antwort

Ich stimme der folgenden Antwort zu, dass die Verwendung von, $injectorum direkt einen Verweis auf den gewünschten Service zu erhalten, nicht ideal ist und als Anti-Muster angesehen werden kann.

Das Ausgeben einer Veranstaltung ist eine viel elegantere und auch entkoppelte Lösung.

Jonathan Palumbo
quelle
1
Wie würde dies für Angular 1.3 angepasst, wo 4 verschiedene Funktionen an Interceptors übergeben werden?
Maciej Gurban
3
Die Verwendung wäre dieselbe. Sie injizieren den $injectorDienst in Ihren Interceptor und rufen an, $injector.get()wo Sie den $stateDienst benötigen .
Jonathan Palumbo
Ich erhalte $ injizot.get ("$ state") als << nicht definiert >>. Können Sie bitte sagen, woran es liegen könnte?
Sandeep Grünkohl
Dies ist definitiv ein Lebensretter, wenn es sich um eine einfache Situation handelt, aber wenn Sie darauf stoßen, ist dies ein Zeichen dafür, dass Sie tatsächlich irgendwo in Ihrem Code eine zirkuläre Abhängigkeit erstellt haben, was in AngularJS mit seinem DI einfach zu bewerkstelligen ist. Misko Hevery erklärt es sehr gut in seinem Blog ; Ich empfehle Ihnen dringend, es zu lesen, um Ihren Code zu verbessern.
JD Smith
2
$httpProvider.responseInterceptors.pushfunktioniert nicht mehr, wenn Sie spätere Versionen von Angular verwenden. Verwenden Sie $httpProvider.interceptors.push()stattdessen. Sie müssten das auch ändern interceptor. Trotzdem danke für die wundervolle Antwort! :)
Shyam
25

Die Frage ist ein Duplikat von AngularJS: Injizieren eines Dienstes in einen HTTP-Interceptor (zirkuläre Abhängigkeit)

Ich poste meine Antwort von diesem Thread hier erneut:

Eine bessere Lösung

Ich denke, die direkte Verwendung des $ -Injektors ist ein Antimuster.

Eine Möglichkeit, die zirkuläre Abhängigkeit zu überwinden, besteht darin, ein Ereignis zu verwenden: Anstatt $ state zu injizieren, injizieren Sie $ rootScope. Anstatt direkt umzuleiten, tun Sie dies

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

Plus

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

Auf diese Weise haben Sie die Bedenken getrennt:

  1. Erkennen Sie eine 401-Antwort
  2. Zur Anmeldung umleiten
Stephen Friedrich
quelle
2
Tatsächlich ist diese Frage ein Duplikat dieser Frage, da diese Frage vor dieser gestellt wurde. Lieben Sie Ihre elegante Lösung, also haben Sie eine positive Bewertung. ;-)
Aaron Gray
1
Ich bin verwirrt darüber, wo ich das ablegen soll. $ RootScope. $ Emit ("nicht autorisiert");
Alex
16

Jonathans Lösung war großartig, bis ich versuchte, den aktuellen Zustand zu retten. In ui-router v0.2.10 scheint der aktuelle Status beim ersten Laden der Seite im Interceptor nicht ausgefüllt zu sein.

Wie auch immer, ich habe es gelöst, indem ich stattdessen das Ereignis $ stateChangeError verwendet habe. Die $ stateChangeError Veranstaltung gibt Ihnen sowohl zu und von Staaten sowie den Fehler. Es ist ziemlich geschickt.

$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");
        }
    });
Justin Wrobel
quelle
Ich habe ein Problem dazu eingereicht .
Justin Wrobel
Ich denke, dass dies mit ngResource nicht möglich ist, da es immer asynchron ist? Ich habe ein paar Sachen ausprobiert, aber ich bekomme keinen Ressourcenaufruf, um eine Zustandsänderung zu verhindern ...
Bastian
4
Was ist, wenn Sie eine Anforderung abfangen möchten, die keine Statusänderung auslöst?
Shikloshi
Sie können den gleichen Ansatz wie @Stephen Friedrich oben verwenden, der diesem tatsächlich ähnelt, jedoch ein benutzerdefiniertes Ereignis verwendet.
Saiyancoder