我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。
我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:
错误:$digest已在进行中
有人知道如何避免这种错误或以不同的方式实现相同的事情吗?
我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。
我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:
错误:$digest已在进行中
有人知道如何避免这种错误或以不同的方式实现相同的事情吗?
当前回答
let $timeoutPromise = null;
$timeout.cancel($timeoutPromise);
$timeoutPromise = $timeout(() => {
$scope.$digest();
}, 0, false);
以下是避免此错误和避免$apply的好解决方案
如果基于外部事件调用,则可以将其与反跳(0)结合使用。以上是我们正在使用的“反跳”,以及完整的代码示例
.factory('debounce', [
'$timeout',
function ($timeout) {
return function (func, wait, apply) {
// apply default is true for $timeout
if (apply !== false) {
apply = true;
}
var promise;
return function () {
var cntx = this,
args = arguments;
$timeout.cancel(promise);
promise = $timeout(function () {
return func.apply(cntx, args);
}, wait, apply);
return promise;
};
};
}
])
以及代码本身,以侦听某些事件并仅在需要的$scope上调用$digest
let $timeoutPromise = null;
let $update = debounce(function () {
$timeout.cancel($timeoutPromise);
$timeoutPromise = $timeout(() => {
$scope.$digest();
}, 0, false);
}, 0, false);
let $unwatchModelChanges = $scope.$root.$on('updatePropertiesInspector', function () {
$update();
});
$scope.$on('$destroy', () => {
$timeout.cancel($update);
$timeout.cancel($timeoutPromise);
$unwatchModelChanges();
});
其他回答
保持此过程干燥的简便小助手方法:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
如果你这样做,有时你仍然会出错(https://stackoverflow.com/a/12859093/801426).
试试看:
if(! $rootScope.$root.$$phase) {
...
看见http://docs.angularjs.org/error/$rootScope:inprog
当对$apply的调用有时在Angular代码外部异步运行(当应使用$apply时),有时在Angle代码内部同步运行(这导致$digest已经在进行中)时,就会出现问题。
例如,当您拥有一个从服务器异步获取项目并缓存它们的库时,可能会发生这种情况。第一次请求项时,将异步检索该项,以免阻止代码执行。然而,第二次,该项已在缓存中,因此可以同步检索。
防止此错误的方法是确保调用$apply的代码异步运行。这可以通过在调用$timeout时运行代码来完成,延迟设置为0(这是默认值)。然而,在$timeout内调用代码就不需要调用$apply,因为$timeout将自行触发另一个$digest循环,从而执行所有必要的更新等。
解决方案
简而言之,不要这样做:
... your controller code...
$http.get('some/url', function(data){
$scope.$apply(function(){
$scope.mydate = data.mydata;
});
});
... more of your controller code...
执行以下操作:
... your controller code...
$http.get('some/url', function(data){
$timeout(function(){
$scope.mydate = data.mydata;
});
});
... more of your controller code...
只有当您知道运行$apply的代码将始终在Angular代码之外运行时,才调用$apply(例如,您对$apply进行的调用将发生在由Angular代码以外的代码调用的回调内)。
除非有人意识到使用$timeout而不是$apply的一些不利影响,否则我不明白为什么不能总是使用$timeout(零延迟)而不是$app,因为它会做大致相同的事情。
从最近与Angular团队就这个主题进行的讨论中可以看出:出于防将来的原因,您不应该使用$$phase
当被问及“正确”的做法时,答案是当前
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
我最近在编写angular服务来包装facebook、谷歌和twitter API时遇到了这种情况,这些API在不同程度上都有回调。
下面是服务中的一个示例。(为了简洁起见,服务的其余部分——设置变量、注入$timeout等——被省略了。)
window.gapi.client.load('oauth2', 'v2', function() {
var request = window.gapi.client.oauth2.userinfo.get();
request.execute(function(response) {
// This happens outside of angular land, so wrap it in a timeout
// with an implied apply and blammo, we're in action.
$timeout(function() {
if(typeof(response['error']) !== 'undefined'){
// If the google api sent us an error, reject the promise.
deferred.reject(response);
}else{
// Resolve the promise with the whole response if ok.
deferred.resolve(response);
}
});
});
});
请注意,$timeout的延迟参数是可选的,如果未设置,则默认值为0($timeout调用$browser.deffer,如果没有设置延迟,则默认为0)
有点不直观,但这是写Angular的人的答案,所以这对我来说足够好了!
尝试使用
$scope.applyAsync(function() {
// your code
});
而不是
if(!$scope.$$phase) {
//$digest or $apply
}
$applysync将$apply的调用安排为稍后进行。这可用于对需要在同一摘要中计算的多个表达式进行排队。
注意:在$digest中,如果当前作用域是$rootScope,则$applySync()将仅刷新。这意味着,如果在子作用域上调用$digest,它将不会隐式刷新$applysync()队列。
Exmaple公司:
$scope.$applyAsync(function () {
if (!authService.authenticated) {
return;
}
if (vm.file !== null) {
loadService.setState(SignWizardStates.SIGN);
} else {
loadService.setState(SignWizardStates.UPLOAD_FILE);
}
});
参考文献:
1.范围.$applySync()与范围$AngularJS 1.3中的evalAsync()
AngularJs文件