Введення $ state (ui-роутера) в перехоплювач $ http викликає кругову залежність


120

Чого я намагаюся досягти

Я хотів би перейти до певного стану (логін), якщо запит $ http поверне помилку 401. Тому я створив перехоплювач $ http.

Проблема

Коли я намагаюся вставити "перешкоду $" у перехоплювач, я отримую кругову залежність. Чому і як це можна виправити?

Код

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

Відповіді:


213

Виправлення

Скористайтеся $injectorпослугою, щоб отримати посилання на $stateслужбу.

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

Причина

angular-ui-router вводить $httpпослугу як залежність, в $TemplateFactoryякій потім створює циркулярну посилання $httpвсередині $httpProviderсебе при відправці перехоплювача.

Це ж виняток із циркулярної залежності буде кинуто, якщо ви спробуєте ввести $httpпослугу безпосередньо в перехоплювач.

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

Розмежування проблем

Винятки з кругової залежності можуть вказувати на те, що у вашій програмі є проблематика, що може спричинити проблеми зі стабільністю. Якщо ви опинитесь за цим винятком, вам слід витратити час на розгляд архітектури, щоб уникнути будь-яких залежностей, які в кінцевому підсумку посилаються на себе.

Відповідь @ Стефана Фрідріха

Я погоджуюся з нижченаведеною відповіддю, що використання $injectorпрямого посилання на потрібну послугу не є ідеальним і може вважатися анти-схемою.

Емітувати подію - це набагато більш елегантне рішення, а також роз'єднане.


1
Як би це було скориговано для кутового 1.3, де в перехоплювачі передані 4 різні функції?
Мацей Гурбан

3
Використання було б таким же, ви вводите $injectorпослугу в перехоплювач і телефонуєте $injector.get()там, де вам потрібно отримати $stateпослугу.
Джонатан Паламбо

Я отримую $ injectot.get ("$ state") як << не визначено >>, скажіть, будь ласка, у чому може бути проблема?
sandeep kale

Це, безумовно, рятувальник, коли це проста ситуація, але коли ви стикаєтеся з цим, це знак того, що ви дійсно створили кругову залежність десь у своєму коді, що легко зробити в AngularJS зі своїм DI. Місько Гевери дуже добре пояснює це у своєму блозі ; настійно рекомендую прочитати його для покращення коду.
JD Smith

2
$httpProvider.responseInterceptors.pushбільше не працюйте, якщо ви використовуєте пізніші версії Angular. Використовуйте $httpProvider.interceptors.push()замість цього. Вам також доведеться змінити interceptor. У будь-якому випадку, дякую за чудову відповідь! :)
шям

25

Питання - це дублікат AngularJS: Ін'єкція послуги в перехоплювач HTTP (кругова залежність)

Я повторно публікую свою відповідь з цієї теми тут:

Краще виправлення

Я думаю, що використання безпосередньо інжектора $ є антипатерном.

Спосіб розірвати кругову залежність - використовувати подію: Замість того, щоб вводити $ state, введіть $ rootScope. Замість того, щоб переспрямовувати безпосередньо, зробіть

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

плюс

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

Таким чином ви розділили проблеми:

  1. Виявити відповідь 401
  2. Перенаправлення на вхід

2
Власне, це питання є дублікатом цього питання, оскільки це питання було задано раніше, ніж це було. Любіть свою елегантну поправку, тому отримайте нагороду. ;-)
Аарон Грей

1
Я плутаю питання про те, куди це поставити. $ RootScope. $ Emit ("несанкціоноване");
alex

16

Рішення Джонатана було чудовим, поки я не намагався врятувати поточний стан. У ui-маршрутизаторі v0.2.10 поточний стан не здається заповненим при початковому завантаженні сторінки в перехоплювачі.

У всякому разі, я вирішив це, використовуючи натомість події $ stateChangeError . $ StateChangeError подія дає вам як до і від держав, а також помилок. Це досить вишукано.

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

Я подав питання про це.
Джастін Вробель

Я думаю, що це неможливо з ngResource, оскільки це завжди асинхрон? Я спробував деякі речі, але мені не дозволено зателефонувати на ресурс, щоб запобігти зміні стану ...
Bastian

4
Що робити, якщо ви хочете перехопити запит, який не викликає зміну стану?
Шиклоші

Ви можете використовувати той самий підхід, що і @Stephen Friedrich, який був вище, який насправді нагадує цей, але використовує спеціальну подію.
saiyancoder
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.