L'injection de $ state (ui-router) dans l'intercepteur $ http provoque une dépendance circulaire

120

Ce que j'essaye d'accomplir

Je voudrais passer à un certain état (connexion) au cas où une requête $ http renvoie une erreur 401. J'ai donc créé un intercepteur $ http.

Le problème

Lorsque j'essaye d'insérer «$ state» dans l'intercepteur, j'obtiens une dépendance circulaire. Pourquoi et comment résoudre ce problème?

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
la source

Réponses:

213

Le correctif

Utilisez le $injectorservice pour obtenir une référence au $stateservice.

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 cause

angular-ui-router injecte le $httpservice comme une dépendance dans $TemplateFactorylaquelle crée alors une référence circulaire à l' $httpintérieur de $httpProviderlui - même lors de l'envoi de l'intercepteur.

La même exception de dépendance circulaire serait lancée si vous essayez d'injecter le $httpservice directement dans un intercepteur comme ceci.

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

Séparation des préoccupations

Les exceptions de dépendance circulaire peuvent indiquer qu'il existe un mélange de préoccupations au sein de votre application, ce qui pourrait entraîner des problèmes de stabilité. Si vous vous trouvez avec cette exception, vous devriez prendre le temps de regarder votre architecture pour vous assurer d'éviter toute dépendance qui finit par se référencer.

Réponse de @Stephen Friedrich

Je suis d'accord avec la réponse ci-dessous selon laquelle l'utilisation du $injector pour obtenir directement une référence au service souhaité n'est pas idéale et pourrait être considérée comme un anti-pattern.

Émettre un événement est une solution beaucoup plus élégante et également découplée.

Jonathan Palumbo
la source
1
Comment cela serait-il ajusté pour Angular 1.3, où 4 fonctions différentes sont transmises aux intercepteurs?
Maciej Gurban
3
L'utilisation serait la même, vous injectez le $injectorservice dans votre intercepteur et appelez $injector.get()là où vous devez obtenir le $stateservice.
Jonathan Palumbo
J'obtiens $ injectot.get ("$ state") comme << non défini >>, pourriez-vous s'il vous plaît dire quel peut être le problème?
sandeep kale
C'est certainement une bouée de sauvetage quand c'est une situation simple, mais lorsque vous rencontrez cela, c'est le signe que vous avez effectivement créé une dépendance circulaire quelque part dans votre code, ce qui est facile à faire dans AngularJS avec sa DI. Misko Hevery l'explique très bien dans son blog ; vous recommande vivement de le lire pour améliorer votre code.
JD Smith
2
$httpProvider.responseInterceptors.pushne fonctionne plus si vous utilisez des versions ultérieures d'Angular. Utilisez $httpProvider.interceptors.push()plutôt. Vous devrez également modifier le interceptorfichier. Quoi qu'il en soit, merci pour la merveilleuse réponse! :)
shyam
25

La question est un double de AngularJS: service d'injection dans un intercepteur HTTP (dépendance circulaire)

Je publie à nouveau ma réponse à partir de ce fil ici:

Une meilleure solution

Je pense que l'utilisation directe de $ injector est un anti-modèle.

Un moyen de rompre la dépendance circulaire est d'utiliser un événement: au lieu d'injecter $ state, injectez $ rootScope. Au lieu de rediriger directement, faites

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

plus

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

De cette façon, vous avez séparé les préoccupations:

  1. Détecter une réponse 401
  2. Rediriger pour se connecter
Stephen Friedrich
la source
2
En fait, cette question est une copie de cette question, car cette question a été posée avant celle-là. J'adore votre solution élégante, alors ayez un vote favorable. ;-)
Aaron Gray
1
Je ne sais pas où mettre cela. $ RootScope. $ Emit ("non autorisé");
alex
16

La solution de Jonathan était excellente jusqu'à ce que j'essaye de sauver l'état actuel. Dans ui-router v0.2.10, l'état actuel ne semble pas être renseigné lors du chargement initial de la page dans l'intercepteur.

Quoi qu'il en soit, je l'ai résolu en utilisant l' événement $ stateChangeError à la place. Le stateChangeError $ si vous donne à la fois à et d' états, ainsi que l'erreur. C'est assez chouette.

$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
la source
J'ai soumis un problème à ce sujet.
Justin Wrobel
Je pense que ce n'est pas possible avec ngResource car il est toujours asynchrone? J'ai essayé des trucs mais je ne reçois pas l'appel de ressource pour empêcher un changement d'état ...
Bastian
4
Que faire si vous souhaitez intercepter une requête qui ne déclenche pas de changement d'état?
Shikloshi
Vous pouvez utiliser la même approche que @Stephen Friedrich ci-dessus, qui ressemble en fait à celle-ci mais utilise un événement personnalisé.
saiyancoder