Внедрение $ state (ui-router) в перехватчик $ http вызывает циклическую зависимость

120

Чего я пытаюсь достичь

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

Эта проблема

Когда я пытаюсь вставить "$ state" в перехватчик, я получаю круговую зависимость. Почему и как мне это исправить?

Код

//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
источник

Ответы:

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

Разделение проблем

Исключения циклических зависимостей могут указывать на то, что в вашем приложении есть смешанные проблемы, которые могут вызвать проблемы со стабильностью. Если вы столкнулись с этим исключением, вам следует потратить время на то, чтобы взглянуть на свою архитектуру, чтобы избежать любых зависимостей, которые в конечном итоге ссылаются на самих себя.

Ответ @Stephen Friedrich

Я согласен с приведенным ниже ответом, что использование $injectorдля прямого получения ссылки на желаемый сервис не идеально и может считаться анти-шаблоном.

Генерация события - гораздо более элегантное и независимое решение.

Джонатан Палумбо
источник
1
Как бы это было скорректировано для Angular 1.3, где в перехватчики передаются 4 разные функции?
Maciej Gurban
3
Использование будет таким же, вы вводите $injectorслужбу в свой перехватчик и вызываете, $injector.get()где вам нужно получить $stateслужбу.
Джонатан Палумбо
Я получаю $ injectot.get ("$ state") как << не определено >>, не могли бы вы рассказать, в чем может быть проблема?
sandeep kale
Это определенно спасает жизнь, когда это простая ситуация, но когда вы сталкиваетесь с этим, это признак того, что вы действительно создали циклическую зависимость где-то в вашем коде, что легко сделать в AngularJS с его DI. Миско Хевери очень хорошо объясняет это в своем блоге ; настоятельно рекомендую вам прочитать его, чтобы улучшить свой код.
JD Smith
2
$httpProvider.responseInterceptors.pushбольше не работают, если вы используете более поздние версии Angular. $httpProvider.interceptors.push()Вместо этого используйте . Вам также придется изменить interceptor. В любом случае спасибо за чудесный ответ! :)
shyam
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 ("unauthorized");
Alex
16

Решение Джонатана было отличным, пока я не попытался сохранить текущее состояние. В ui-router 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, поскольку он всегда асинхронен? Я пробовал кое-что, но не получил вызова ресурса, чтобы предотвратить изменение состояния ...
Бастиан
4
Что, если вы хотите перехватить запрос, который не вызывает изменения состояния?
Shikloshi
Вы можете использовать тот же подход, что и @Stephen Friedrich выше, который на самом деле похож на этот, но использует настраиваемое событие.
saiyancoder