Angular HTTP 拦截器后视图未更新

Angular View doesnt update after http interceptor

我在控制器中使用 promise 在 $http 请求后呈现视图。一切正常,除非在 401 错误期间涉及 http 拦截器。当发生这种情况时,这就是我面临的问题:

  1. http 拦截器从 api 中获取 401 错误,添加 被拒绝的查询缓冲并显示登录模式。

  2. 登录成功后,排队的api个查询 之前的401再试一次 api returns 请求的数据 成功.

  3. 但是,即使数据是从 api 返回的, angular 视图未更新。视图仅在 手动刷新该状态。

这些是我的代码:

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 的获取请求。

如果为上述触发了 auth 拦截器,我可以看到 $http $_GET 在成功登录后再次尝试,但 $scope.namesList 未在视图中更新。

目前想到的:

1) 我正在考虑为上面添加一个额外的 $watch 以便它在 http 拦截器之后工作。我想到了这一点,然后放弃了该选项,因为如果我在一个页面(如仪表板)中有多个获取请求,那么观察每个请求都可能过火。

2) 如下图登录后手动刷新ui路由器状态。这行得通,但是当视图涉及表单提交时它没有帮助,其中完成的表单将在状态重新加载后重置:

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

所以我知道如何解决这个问题。有人可以 guide me here please....

编辑: 在@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);
        }
      };
    }]);

这是我目前正在使用的正在运行的:

1) 删除了 angular.forEach 并将其替换为简单的 if 语句。由于我只检查了几个条件,if 是我认为的一个不错的选择。

2) 我在 if 语句中添加了 deferred.promise,之后一切正常。登录后视图得到更新,控制器可以继续解析从 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 指出错误:

Your return deferred.promise is inside the angular.forEach. That means that it's returning inside that function, not to the promise handler. You'll need to assign that to a variable outside of the forEach function and return that way.

很高兴看到您的回答有效。如果愿意,您仍然可以使用 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 拦截器中 not功能。因此,您有以下三种选择:

  1. 您可以使用传统的 javascript for 循环
  2. 您可以使用 if 条件语句
  3. 您可以使用您跟踪的 "external" 变量

您的示例使用的是第二种方法,但如果您仍想使用 angular.forEach,我上面的代码是第三种方法的演示。第一种方法也是可行的,但不太常见,而且有点混乱。