Angular HTTP 拦截器后视图未更新
Angular View doesnt update after http interceptor
我在控制器中使用 promise 在 $http
请求后呈现视图。一切正常,除非在 401 错误期间涉及 http 拦截器。当发生这种情况时,这就是我面临的问题:
http 拦截器从 api 中获取 401 错误,添加
被拒绝的查询缓冲并显示登录模式。
登录成功后,排队的api个查询
之前的401再试一次 api returns 请求的数据
成功.
但是,即使数据是从 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功能。因此,您有以下三种选择:
- 您可以使用传统的 javascript
for
循环
- 您可以使用
if
条件语句
- 您可以使用您跟踪的 "external" 变量
您的示例使用的是第二种方法,但如果您仍想使用 angular.forEach
,我上面的代码是第三种方法的演示。第一种方法也是可行的,但不太常见,而且有点混乱。
我在控制器中使用 promise 在 $http
请求后呈现视图。一切正常,除非在 401 错误期间涉及 http 拦截器。当发生这种情况时,这就是我面临的问题:
http 拦截器从 api 中获取 401 错误,添加 被拒绝的查询缓冲并显示登录模式。
登录成功后,排队的api个查询 之前的401再试一次 api returns 请求的数据 成功.
但是,即使数据是从 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功能。因此,您有以下三种选择:
- 您可以使用传统的 javascript
for
循环 - 您可以使用
if
条件语句 - 您可以使用您跟踪的 "external" 变量
您的示例使用的是第二种方法,但如果您仍想使用 angular.forEach
,我上面的代码是第三种方法的演示。第一种方法也是可行的,但不太常见,而且有点混乱。