【问题标题】:Angular View doesnt update after http interceptorhttp拦截器后Angular View不更新
【发布时间】:2015-07-27 04:05:02
【问题描述】:

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

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

  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) 登录后手动刷新 ui 路由器状态,如下所示。这可行,但当视图涉及表单提交时,它没有帮助,其中完成的表单将在状态重新加载后被重置:

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

所以我确定如何解决这个问题。有人可以在这里指导我吗....

编辑: 在 @ChrisFoster 在 cmets 中的建议之后,我将 return deferred.promise 移到了 angular.forEach 之外,并稍微更改了 foreach 检查。这似乎在登录后正确更新了视图。但是,现在模式显示每个错误,而不仅仅是拒绝原因中列出的错误:

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

【问题讨论】:

  • 您的return deferred.promise 位于内部 angular.forEach。这意味着它在该函数内部返回,而不是返回到 Promise 处理程序。您需要将其分配给forEach 函数之外的变量并以这种方式返回。我不确定这是否是你的全部问题,但它是它的一部分:)
  • @ChrisFoster 我认为你是对的。当我在forEach 循环之外删除return deferred.promise 时,视图更新正常。所以你已经发现了我的错误。但是,我仍然坚持如何正确解决它。我的意思是,我稍微更改了 foreach,因此它会检查不等于并在不继续 deferred.promise 时返回默认行为。但是,当我将 deferred.promise 移到 foreach 之外时,模式会显示每 400 个错误,而不是我在拒绝原因下列出的错误。推迟和承诺的事情让我很困惑,我不知道是非。
  • 我用我的错误尝试更新了我的问题。你能帮我吗?仍然坚持下去:(
  • @ChrisFoster 最后我让它工作了。我必须阅读基础知识并花一些时间学习promiseangular forEach。后来我意识到我错了。我现在使用了一个简单的if 语句并在其中处理deferred.promise。这似乎有效。我已将其添加为答案,因为这是我目前正在使用的解决方案。如果您觉得可以以更有效的方式完成,请告诉我。非常感谢您的意见。你50字的评论给了我丰富的知识。 :)
  • 很高兴你能成功!我很高兴你能弄清楚发生了什么。您当前的答案非常好,但如果您仍然想知道如何使用 angular.forEach 来解决问题,请查看我在下面添加的答案 :)

标签: angularjs angularjs-scope angular-ui-router angular-services angular-http-interceptors


【解决方案1】:

很高兴看到您的回答有效。如果您愿意,您仍然可以使用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,我上面的代码是第三种方法的演示。第一种方法也是可能的,但不太常见,而且有点混乱。

【讨论】:

  • 完美。这帮助了很多,因为它对如何使用 angular.forEach 循环来解决这个问题更有意义。我现在将我的代码从if 语句再次更改为angular.forEach,因为将来向rejectionReason 数组添加更多原因会更容易。再次感谢您向我展示这个示例。
  • @neel 没问题,编码愉快!
【解决方案2】:

这是我目前正在使用的:

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:

您的返回 deferred.promise 在 angular.forEach 内。这意味着 它在该函数内部返回,而不是返回到 promise 处理程序。 您需要将其分配给 forEach 之外的变量 函数并以这种方式返回。

【讨论】:

    猜你喜欢
    • 2023-03-13
    • 1970-01-01
    • 1970-01-01
    • 2017-11-07
    • 2018-05-19
    • 1970-01-01
    • 2019-08-03
    • 2018-07-08
    • 2020-11-08
    相关资源
    最近更新 更多