angularjs - 在调用$scope.$apply时, 防止出现$digest错误

  显示原文与译文双语对照的内容

在 Angular 中构建应用程序之后,我发现我需要更多地手动更新我的页面到我的作用域。 我知道这样做的唯一方法是从控制器和指令的作用域调用 $apply() 。 问题是,它总是向控制台抛出一个错误,它读取

错误:$digest 已经在进行中

有谁知道如何避免这种错误或者实现相同的方式,但另一种方式?

时间:

通过检查你可以检查如果一个 $digest 已经在进行中


if(!$scope.$$phase) {
//$digest or $apply
}

在 progress, $scope.$$phase 将返回"$digest"或者"$apply"如果一个 $digest 或者 $apply 包是 我相信这些状态之间的差异在于 $digest 将处理当前作用域的监视,它是孩子,$apply 将处理所有作用域的观察者。

对于dnc253来说,如果你发现你经常调用 $digest 或者 $apply,你可能会做错。 一般情况下,当我需要将范围的状态更新为 Angular 事件之外的DOM事件时,我需要进行摘要。 例如当 Twitter Bootstrap 模式隐藏时。 有时当 $digest 正在进行时,会激发DOM事件,有时。 这就是为什么我使用这个检查。

我想知道一个更好的方法如果任何人都知道的话。

从评论:按 @anddoutoi

angular.js 反模式

  1. 不做 if (!$scope.$$phase) $scope.$apply() 这意味着你的$scope.$apply() 在调用堆栈中不够高。

在最近与 Angular 人员讨论这个话题的时候: 于future-proofing相关原因,你不应使用 $$phase.

当按"右边"来完成它时,答案是


$timeout(function() {
//anything you want can go here and will safely be run on the next digest.
})

在这里写 Angular 时,我最近碰到服务在 facebook,谷歌,和 Twitter api来包装它,在不同程度上,有回调 handed.

下面是一个来自服务内的示例。 ( 为了简洁起见,设置了变量的服务 --,注入了 $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.defer 哪个默认为 0如果拖延不设置 )

一个小 non-intuitive,但这是写 Angular的答案,所以对我来说足够了 !

摘要循环是同步调用。 它将不会对浏览器循环的事件产生控制权,直到完成。 有几种方法可以处理这个问题。 处理这里问题最简单的方法是使用内置的$timeout,,第二种方法是使用下划线或者 lodash ( 你应该是),调用以下内容:


$timeout(function(){
//any code in here will automatically have an apply run afterwards
});

或者如果你有下划线:


_.defer(function(){$scope.$apply();});

我们尝试了几种方法,我们痛恨将 $rootScope 注入到所有控制器,指令甚至一些工厂中。 所以,$timeout 和 _.defer 一直是我们最喜欢的。 这些方法成功地告诉 Angular 等待下一个动画循环,这将保证当前作用域。$apply结束。

这里的许多答案都包含好的建议,但也会导致混乱。 只需使用 $timeout 不是最好的,也没有合适的解决方案。 另外,如果你关注性能或者可伸缩性,请务必阅读。

你应该知道的东西

  • $$phase 对框架是私有的,并且有很好的理由。

  • $timeout(callback) 将等待当前摘要循环( 如果有的话) 完成,然后执行 callack,然后在结束时运行完整的$apply

  • $timeout(callback, delay, false) 将执行相同的( 在执行回调之前有一个可选延迟),但不会触发 $apply ( 第三个参数),如果你没有修改 Angular 模型( $scope ),它将保存性能。

  • $scope.$apply(callback) 在其他方面调用 $rootScope.$digest,这意味着它将redigest应用程序的根作用域及其所有子作用域,即使你在独立作用域内。

  • $scope.$digest() 将简单地同步它的模型到视图,但不会消化其父作用域,这可以在使用独立的作用域( 大部分来自指令) 处理HTML的独立部分时节省大量的性能。 $digest 不接受回调: 执行代码,然后摘要。

  • $scope.$evalAsync(callback) 已经与 angularjs 1.2一起引入,它可能会解决大多数问题。 请参考最后一段以了解更多信息。

  • 如果你得到了 $digest already in progress error ,那么你的架构是错误的: 负责那个要么你不需要在范围之内或者 redigest不应 be.

如何构造代码

当你遇到这里错误时,你正在试图对作用域进行摘要,但它正在进行中: 因为你不知道你的范围的状态,你不负责处理它的消化。


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 。基本上,它将在当前摘要循环中执行它的回调,否则,新的摘要循环将开始执行回调。

引领国内一个资源都潜在 cached,,它仍然不是一个 $scope.$digest 一样好如果你真的知道只需要同步你的一个孤立的部分 HTML ( 因为没有一个新的$apply 正在进行,如果没有) 这你不能知道它,但这是最好的解决方法当你在执行该函数为instance:如果将同步或者不它的执行, 有时这需要对服务器进行异步调用,否则资源将被同步地获取。

在这些情况和所有你拥有 $scope.$$phase的地方,一定要使用 $scope.$evalAsync( callback )!

方便的小 helper 方法使这个过程保持干燥:


function safeApply(scope, fn) {
 (scope.$$phase || scope.$root.$$phase)? fn() : scope.$apply(fn);
}

当你收到这里错误时,它基本上意味着它已经在更新你的视图。 你真的不需要在控制器中调用 $apply() 。 如果你的视图没有像预期的那样更新,那么在调用 $apply() 之后就会出现这个错误,这很可能意味着你没有正确更新模型。 如果你发布一些细节,我们可以找出核心问题。

我与第三方脚本( 比如 CodeMirror ) 和Krpano的脚本有同样的问题,甚至使用这里提到的safeApply方法还没有解决这个错误。

但是它已经解决了使用 $timeout 服务( 别忘了先注入它) 。

因此,类似于:


$timeout(function() {
//run my code safely here
})

如果你在你的代码里

这个

可能是因为它位于指令的工厂控制器中,或者只是需要某种绑定,那么你可以执行以下操作:


.factory('myClass', [
 '$timeout',
 function($timeout) {

 var myClass = function() {};

 myClass.prototype.surprise = function() {
//Do something suprising! :D
 };

 myClass.prototype.beAmazing = function() {
//Here 'this' referes to the current instance of myClass

 $timeout(angular.bind(this, function() {
//Run my code safely here and this is not undefined but
//the same as outside of this anonymous function
 this.surprise();
 }));
 }

 return new myClass();

 }]
)

希望这能节省你的时间。

此致

查看 http://docs.angularjs.org/error/web $rootScope:inprog

当调用 $apply的时候会出现问题,它有时会在 Angular 代码( 什么时候使用 $apply ) 之外异步运行,有时会在 Angular 代码内部同步运行( 这会导致这个问题) 。 $digest already in progress 错误) 。

这可能会发生,例如当你有一个库从服务器异步获取项目并缓存它们时。 第一次请求项时,将异步检索它,以免阻止代码执行。 第二次,然而,项目已经在缓存中,所以可以同步检索。

防止这里错误的方法是确保调用 $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的调用将发生在由 Angular 代码之外的代码调用的回调内。

除非有人在注意一些影响力的缺点于使用 $timeout$apply,我不明白为什么你们不能总是使用 $timeout ( 零延迟) 代替大约 $apply,因为它要做的同样的事情。

如果使用这种方式,有时仍然会得到错误( http://stackoverflow.com 12859093/801426 ) 。

尝试这个:


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

你也可以使用 evalAsync 。 它将在摘要完成后运行 !


scope.evalAsync(function(scope){
//use the scope...
});

...