Wstrzyknięcie $ state (ui-router) do przechwytywacza $ http powoduje cykliczną zależność

120

Co próbuję osiągnąć

Chciałbym przejść do określonego stanu (logowania) na wypadek, gdyby żądanie $ http zwróciło błąd 401. Dlatego utworzyłem przechwytywacz $ http.

Problem

Kiedy próbuję wstawić „stan $” do przechwytywacza, otrzymuję zależność cykliczną. Dlaczego i jak to naprawić?

Kod

//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
źródło

Odpowiedzi:

213

Poprawka

Skorzystaj z $injectorusługi, aby uzyskać odniesienie do $stateusługi.

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

Przyczyna

skośne ui routera wstrzykuje $httpusługi zgodnie z zależnością w $TemplateFactoryktórej następnie tworzy kołową odnosi się $httpw $httpProvidersiebie na dysponowanie przechwytywania.

Ten sam cykliczny wyjątek zależności zostałby zgłoszony, jeśli spróbujesz wstrzyknąć $httpusługę bezpośrednio do przechwytywacza, tak jak to.

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

Rozdzielenie problemów

Wyjątki od zależności cyklicznych mogą wskazywać, że w aplikacji występuje mieszanka problemów, które mogą powodować problemy ze stabilnością. Jeśli znajdziesz się z tym wyjątkiem, powinieneś poświęcić trochę czasu na przyjrzenie się swojej architekturze, aby upewnić się, że unikniesz wszelkich zależności, które kończą się odwoływaniem do siebie.

Odpowiedź @Stephen Friedricha

Zgadzam się z odpowiedzią poniżej, że użycie polecenia w $injectorcelu bezpośredniego uzyskania odniesienia do pożądanej usługi nie jest idealne i można je uznać za anty-wzorzec.

Emisja zdarzenia jest rozwiązaniem znacznie bardziej eleganckim i odsprzęgniętym.

Jonathan Palumbo
źródło
1
Jak można by to dostosować do Angular 1.3, gdzie są 4 różne funkcje przekazywane do przechwytywaczy?
Maciej Gurban,
3
Użycie byłoby takie samo, wstrzykujesz $injectorusługę do przechwytywacza i dzwonisz $injector.get()tam, gdzie potrzebujesz $stateusługi.
Jonathan Palumbo,
Otrzymuję $ injectot.get ("$ state") jako << nieokreślone >>, czy mógłbyś powiedzieć, co może być przyczyną?
sandeep jarmuż
To z pewnością ratuje życie, gdy jest to prosta sytuacja, ale kiedy napotkasz to, oznacza to, że rzeczywiście utworzyłeś zależność cykliczną gdzieś w swoim kodzie, co jest łatwe do zrobienia w AngularJS dzięki jego DI. Misko Hevery bardzo dobrze wyjaśnia to na swoim blogu ; gorąco polecam przeczytanie go w celu ulepszenia kodu.
JD Smith
2
$httpProvider.responseInterceptors.pushnie działają już, jeśli używasz nowszych wersji Angulara. Użyj $httpProvider.interceptors.push()zamiast tego. Musiałbyś również zmodyfikować interceptor. W każdym razie dzięki za wspaniałą odpowiedź! :)
shyam
25

Pytanie jest duplikatem AngularJS: wstrzykiwanie usługi do przechwytywacza HTTP (zależność cykliczna)

Ponownie zamieszczam odpowiedź z tego wątku tutaj:

Lepsze rozwiązanie

Myślę, że użycie wtryskiwacza $ bezpośrednio jest przeciwieństwem.

Sposobem na przerwanie zależności cyklicznej jest użycie zdarzenia: zamiast wstrzykiwać $ state, wstrzyknij $ rootScope. Zamiast przekierowywać bezpośrednio, zrób

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

plus

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

W ten sposób rozdzieliliście obawy:

  1. Wykryj odpowiedź 401
  2. Przekieruj do logowania
Stephen Friedrich
źródło
2
Właściwie to pytanie jest powtórzeniem tego pytania, ponieważ to pytanie zostało zadane wcześniej. Uwielbiam jednak swoją elegancką poprawkę, więc miejcie pozytywną opinię. ;-)
Aaron Gray
1
Nie wiem, gdzie to umieścić. $ RootScope. $ Emit ("nieautoryzowane");
Alex
16

Rozwiązanie Jonathana było świetne, dopóki nie spróbowałem zapisać obecnego stanu. W ui-router v0.2.10 wydaje się, że bieżący stan nie jest zapełniany przy początkowym ładowaniu strony w module przechwytującym.

W każdym razie rozwiązałem to, używając zamiast tego zdarzenia $ stateChangeError . $ StateChangeError wydarzenie daje zarówno do i z państw, jak również błędu. To całkiem fajne.

$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
źródło
Zgłosiłem problem na ten temat.
Justin Wrobel
Myślę, że nie jest to możliwe z ngResource, ponieważ zawsze jest asynchroniczny? Próbowałem kilku rzeczy, ale nie otrzymałem wezwania do zasobów, aby zapobiec zmianie stanu ...
Bastian
4
A co, jeśli chcesz przechwycić żądanie, które nie powoduje zmiany stanu?
Shikloshi
Możesz użyć tego samego podejścia, które zrobił @Stephen Friedrich powyżej, które faktycznie przypomina to, ale używa niestandardowego zdarzenia.
saiyancoder