Angular View не обновляется после http-перехватчика

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

  1. Перехватчик http обнаруживает ошибки 401 из API, добавляет отклоненный запрос в буфер и показывает модальный вход.

  2. После успешного входа в систему запрашиваемые API-запросы в очереди, которые ранее были 401, повторяются, и API успешно возвращает запрошенные данные.

  3. Однако, что происходит, даже когда данные возвращаются из API, угловой вид не обновляется. Представление обновляется только после обновления этого состояния вручную.

Вот мои коды:

1) http перехватчик:

я использую http-auth-interceptor в этом

$httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) {
          return {
            responseError: function(rejection) {
              if (!rejection.config.ignoreAuthModule) {

                  var rejectionReasons = ['token_not_provided', 'token_expired', 'token_absent', 'token_invalid'];

                  angular.forEach(rejectionReasons, function(value, key) {

                       if(rejection.data.error === value) {
                         var deferred = $q.defer();
                         httpBuffer.append(rejection.config, deferred);
                         $rootScope.$broadcast('event:auth-loginRequired', rejection);
                         return deferred.promise;
                       }
                  })

              }
              // otherwise, default behaviour
              return $q.reject(rejection);
            }
          };
        }]);

2) http перехватчик смотреть:

scope.$on('event:auth-loginRequired', function() {
    LoginModalService.openLoginModal();
    console.log('login needed');
});
scope.$on('event:auth-loginCancelled', function() {
        $state.go('index');
});

3) Контроллер:

.controller('FetchData', ['$scope', 'DataService', 
            function($scope, DataService) {

            DataService.fetchNamesList()
            .success(function(names) {
                $scope.namesList = names;
            }).error(function(error) {
            // The login modal should show if auth error occurs
            });         

}])         

Здесь DataService.fetchNamesList() делает запрос на получение от API.

Если для вышеуказанного срабатывает аутентификационный перехватчик, я вижу $http $_GET повторяется после успешного входа в систему, но $scope.namesList не обновляется в поле зрения.

Что я думал до сих пор:

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

2) Вручную обновите состояние маршрутизатора пользовательского интерфейса после входа в систему, как показано ниже. Это работает, но это не помогает, когда представление включает отправку формы, где заполненная форма будет сброшена после перезагрузки состояния:

$state.go($state.current, {}, {reload: true});

Поэтому я уверен, как решить эту проблему. Может кто-нибудь направить меня сюда, пожалуйста....

РЕДАКТИРОВАТЬ: После предложения @ChrisFoster в комментариях я переместил return deferred.promise вне angular.forEach и немного изменил проверку foreach. Кажется, это корректно обновляет вид после входа в систему. Тем не менее, теперь модальные показы для всех ошибок, а не только те, что перечислены в rejectionReasons:

  $httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) {
      return {
        responseError: function(rejection) {
          if (!rejection.config.ignoreAuthModule) {

              var rejectionReasons = ['token_not_provided', 'token_expired', 'token_absent', 'token_invalid'];

              // Loop through each rejection reason and redirect
              angular.forEach(rejectionReasons, function(value, key) {

                   if(!(rejection.data.error === value)) {
                      return $q.reject(rejection);
                   }

              });

              var deferred = $q.defer();
              httpBuffer.append(rejection.config, deferred);
              $rootScope.$broadcast('event:auth-loginRequired', rejection);
              return deferred.promise;

          }
          // otherwise, default behaviour
          return $q.reject(rejection);
        }
      };
    }]);

2 ответа

Решение

Рад, что ваш ответ работает. Вы все еще можете использовать angular.forEach Если хотите, вот пример того, как это будет выглядеть:

$httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer', function($rootScope, $q, httpBuffer) {
  return {
    responseError: function(rejection) {

      var loginRequired = false;
      var rejectionReasons = [
        'token_not_provided', 'token_expired',
        'token_absent', 'token_invalid'
      ];

      // Loop through each rejection reason
      angular.forEach(rejectionReasons, function(value, key) {
        if (!(rejection.data.error === value)) {
          // Set loginRequired, and check this later
          // We *can't* return here because we are inside a function :)
          loginRequired = true;
        }
      });

      // Create a promise
      var deferred = $q.defer();

      if (loginRequired && !rejection.config.ignoreAuthModule) {
        // Display a login module and queue a retry request
        httpBuffer.append(rejection.config, deferred);
        $rootScope.$broadcast('event:auth-loginRequired', rejection);
      } else {
        // Not a auth error, or ignoreAuthModule is enabled
        // Therefore immediately reject the promise with the fail value
        deferred.reject(rejection);
      }

      // Return the promise
      return deferred.promise;
    }
  };
}]);

Причина, по которой вы столкнулись с проблемами forEach потому что вы передаете функцию в качестве аргумента forEach, Когда ты return внутри вы возвращаете это значение в forEach функция, а не в функции перехватчика HTTP. Поэтому у вас есть три варианта:

  1. Вы могли бы использовать традиционный JavaScript for петля
  2. Вы могли бы использовать if заявление условное
  3. Вы можете использовать "внешнюю" переменную, которую вы отслеживаете

Ваш пример использует второй подход, но если вы все еще хотите использовать angular.forEach мой код выше является демонстрацией третьего подхода. Первый подход также возможен, но немного менее распространен и немного более грязен.

Это то, что я использую в данный момент, который работает:

1) Удален angular.forEach и заменил это простым if заявление. Так как я проверяю только несколько условий, if не слишком плохая альтернатива, я думал.

2) я добавил deferred.promise внутри if заявление и после этого все работало нормально. Представление обновлялось после входа в систему, и у контроллера не было проблем с продолжением разрешения данных, возвращаемых из API.

Это мой код:

  $httpProvider.interceptors.push(['$rootScope', '$q', 'httpBuffer',
      function($rootScope, $q, httpBuffer) {
        return {
          responseError: function(rejection) {
            if (!rejection.config.ignoreAuthModule) {

                var rejectionReasons = ['token_not_provided', 'token_expired', 'token_absent', 'token_invalid'];

                // Loop through each rejection reason and redirect
                if (
                      rejection.data.error === 'token_not_provided' ||
                      rejection.data.error === 'token_expired' ||
                      rejection.data.error === 'token_absent' ||
                      rejection.data.error === 'token_invalid'

                    )
                {
                  var deferred = $q.defer();
                  httpBuffer.append(rejection.config, deferred);
                  $rootScope.$broadcast('event:auth-loginRequired', rejection);
                  return deferred.promise;
                }

            }
            // otherwise, default behaviour
            return $q.reject(rejection);
          }
        };
      }]);

Спасибо @ChrisFoster, который указал на ошибку:

Ваше возвращение deferred.promise находится внутри angular.forEach. Это означает, что он возвращается внутри этой функции, а не в обработчик обещаний. Вам нужно будет присвоить это переменной за пределами функции forEach и вернуть таким образом.