我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。
我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:
错误:$digest已在进行中
有人知道如何避免这种错误或以不同的方式实现相同的事情吗?
我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。
我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:
错误:$digest已在进行中
有人知道如何避免这种错误或以不同的方式实现相同的事情吗?
当前回答
这里的许多答案都包含很好的建议,但也可能导致混淆。简单地使用$timeout既不是最好的解决方案,也不是正确的解决方案。此外,如果您关注性能或可伸缩性,请务必阅读。
你应该知道的事情
$$阶段对框架是私有的,这有充分的理由。$timeout(回调)将等待当前摘要周期(如果有)完成,然后执行回调,然后在结束时运行完整的$apply。$timeout(callback,delay,false)将执行相同的操作(在执行回调之前有一个可选的延迟),但不会触发$apply(第三个参数),如果您没有修改Angular模型($scope),则会节省性能。$范围$apply(callback)调用$rootScope等$digest,这意味着它将重新划分应用程序的根范围及其所有子范围,即使您在一个独立的范围内。$范围$digest()将简单地将其模型同步到视图,但不会消化其父范围,这可以在使用隔离范围(主要来自指令)处理HTML的隔离部分时节省大量性能$摘要不接受回调:执行代码,然后进行摘要。$范围$angularjs 1.2引入了evalAsync(回调),它可能会解决大部分问题。请参考最后一段了解更多信息。如果你得到了$digest已经在进行中的错误,那么你的架构是错误的:要么你不需要重新定义你的范围,要么你不应该负责这个(见下文)。
如何构造代码
当你遇到这个错误时,你正在试图消化你的范围,而它已经在进行中:因为你不知道你的范围在那一点上的状态,你不负责处理它的消化。
function editModel() {
$scope.someVar = someVal;
/* Do not apply your scope here since we don't know if that
function is called synchronously from Angular or from an
asynchronous code */
}
// Processed by Angular, for instance called by a ng-click directive
$scope.applyModelSynchronously = function() {
// No need to digest
editModel();
}
// Any kind of asynchronous code, for instance a server request
callServer(function() {
/* That code is not watched nor digested by Angular, thus we
can safely $apply it */
$scope.$apply(editModel);
});
如果您知道在大型Angular应用程序的一部分时正在执行和处理一个孤立的小指令,那么您可以选择$digest而不是$apply来节省性能。
从Angularjs 1.2开始更新
任何$scope:$evalAsync中都添加了一个新的强大方法。基本上,如果发生回调,它将在当前摘要周期内执行回调,否则新的摘要周期将开始执行回调。
这仍然不如$scope$如果你真的知道你只需要同步HTML的一个孤立部分(因为如果没有正在进行的话,会触发一个新的$apply),那么这是最好的解决方案,但是当你执行一个你不知道是否会同步执行的函数时,例如在获取一个可能缓存的资源之后,否则将同步本地获取资源。
在这些情况下,以及所有其他情况下$范围$$阶段,请确保使用$scope$evalAsync(回调)
其他回答
尝试使用
$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文件
找到此:https://coderwall.com/p/ngisma其中Nathan Walker(靠近页面底部)建议在$rootScope中使用decorator来创建func“safeApply”,代码:
yourAwesomeModule.config([
'$provide', function($provide) {
return $provide.decorator('$rootScope', [
'$delegate', function($delegate) {
$delegate.safeApply = function(fn) {
var phase = $delegate.$$phase;
if (phase === "$apply" || phase === "$digest") {
if (fn && typeof fn === 'function') {
fn();
}
} else {
$delegate.$apply(fn);
}
};
return $delegate;
}
]);
}
]);
当您收到此错误时,基本上意味着它已经在更新视图的过程中。您真的不需要在控制器中调用$apply()。如果您的视图没有按预期进行更新,并且在调用$apply()后出现此错误,那么很可能意味着您没有正确更新模型。如果你发布一些细节,我们可以找出核心问题。
不要使用这种模式-这将导致比它解决的错误更多的错误。即使你认为它修复了一些东西,但它没有。
您可以通过检查$scope来检查$digest是否已在进行中$$阶段
if(!$scope.$$phase) {
//$digest or $apply
}
$范围$$如果正在进行$digest或$apply,则阶段将返回“$digest”或“$apply”。我认为这些状态之间的区别在于,$digest将处理当前范围及其子范围的监视,而$apply将处理所有范围的监视。
就@dnc253而言,如果你发现自己经常调用$digest或$apply,那么你可能做得不对。我通常发现,当我需要更新作用域的状态时,我需要进行消化,因为DOM事件超出Angular的范围。例如,当twitter引导模式变为隐藏时。有时,当$digest正在进行时,DOM事件会触发,有时则不会。这就是我用这张支票的原因。
如果有人知道,我很想知道更好的方法。
来自评论:作者@anddoutoi
angular.js反模式
不执行if(!$scope.$$phase)$scope$apply(),表示$scope$apply()在调用堆栈中不够高。
在为我们创建一个可重用的$safeApply函数方面,yearmomo做得很好:
https://github.com/yearofmoo/AngularJS-Scope.SafeApply
用法:
//use by itself
$scope.$safeApply();
//tell it which scope to update
$scope.$safeApply($scope);
$scope.$safeApply($anotherScope);
//pass in an update function that gets called when the digest is going on...
$scope.$safeApply(function() {
});
//pass in both a scope and a function
$scope.$safeApply($anotherScope,function() {
});
//call it on the rootScope
$rootScope.$safeApply();
$rootScope.$safeApply($rootScope);
$rootScope.$safeApply($scope);
$rootScope.$safeApply($scope, fn);
$rootScope.$safeApply(fn);