我发现自从在angular中构建应用程序以来,我越来越需要手动将页面更新到我的范围。

我知道的唯一方法是从控制器和指令的范围调用$apply()。这样做的问题是,它不断向控制台抛出一个错误,错误如下:

错误:$digest已在进行中

有人知道如何避免这种错误或以不同的方式实现相同的事情吗?


当前回答

看见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,因为它会做大致相同的事情。

其他回答

如果你这样做,有时你仍然会出错(https://stackoverflow.com/a/12859093/801426).

试试看:

if(! $rootScope.$root.$$phase) {
...

了解到Angular文档将检查$$阶段称为反模式,我尝试使用$timeout和_.deffer来工作。

超时和延迟方法在dom中创建一个未解析的{{myVar}}内容的闪存,就像FOUT一样。对我来说,这是不可接受的。这让我没有太多教条地告诉我,有些东西是黑客,没有合适的选择。

每次唯一有效的方法是:

if(scope.$$phase!==“$digest”){scope.$digest()}。

我不明白这种方法的危险性,也不明白为什么评论中的人和棱角分明的团队会把它描述为黑客。命令看起来很精确,很容易理解:

“做摘要,除非已经发生了”

在CoffeeScript中,它更漂亮:

范围$digest(),除非作用域$$阶段为“$digest”

这有什么问题?有没有替代方案不会产生FOUT$safeApply看起来不错,但也使用$$阶段检查方法。

使用$scope$$阶段||$scope$apply();相反

这里的许多答案都包含很好的建议,但也可能导致混淆。简单地使用$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(回调)

在为我们创建一个可重用的$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);