我所知道的唯一方法是从我的控制器和指令的范围内调用`$apply()`。但问题是,它会不断在控制台抛出以下错误:
"Error: $digest already in progress"
有人知道如何避免这个错误或以不同的方式实现相同的操作吗?
最近与Angular团队就这个问题进行了讨论:出于未来的考虑,你不应该使用$$phase
当被追问正确的做法时,目前的答案是:
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
最近我在编写Angular服务时遇到了这个问题,需要封装Facebook、Google和Twitter 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.defer,如果未设置延迟,则默认为0)
有点不直观,但这是Angular开发人员的答案,对我来说已经足够好了!
$timeout
而不是原生的setTimeout
,为什么不使用$window
代替原生的window
? - Lee Goddard$timeout
的目的是,$timeout
可以确保Angular作用域得到正确更新。如果当前没有进行$digest,则会触发新的$digest运行。 - awe不要使用这种模式 - 这会导致更多的错误而不是解决问题。即使你认为它修复了一些问题,实际上并没有。
你可以通过检查$scope.$$phase
来确定当前是否已经有一个$digest正在进行中。
if(!$scope.$$phase) {
//$digest or $apply
}
$scope.$$phase
将返回"$digest"
或"$apply"
,如果正在进行$digest
或$apply
。我认为这两种状态之间的区别在于,$digest
将处理当前作用域及其子级的监视器,而$apply
将处理所有作用域的监视器。
针对@dnc253的观点,如果您发现自己经常调用$digest
或$apply
,那么您可能做错了什么。一般情况下,我要更新作用域的状态以响应Angular范围之外的DOM事件的触发时,需要使用digest。例如,当Twitter Bootstrap模态框隐藏时。有时DOM事件会在$digest
正在进行时触发,有时不会。这就是我使用这个检查的原因。
如果有人知道更好的方法,我很想知道。
来自评论:
- 不要执行
if (!$scope.$$phase) $scope.$apply()
,这意味着您的$scope.$apply()
不够高。
if (!$scope.$$phase) $scope.$apply()
,请参考 https://github.com/angular/angular.js/wiki/Anti-Patterns。 - anddoutoi“脏值检测”是同步调用。它在完成之前不会将控制权交还给浏览器的事件循环。有几种方法可以解决这个问题。最简单的方法是使用内置的 $timeout,第二种方法是如果您正在使用underscore或lodash(应该如此),请调用以下内容:
$timeout(function(){
//any code in here will automatically have an apply run afterwards
});
或者如果您有lodash:
_.defer(function(){$scope.$apply();});
我们尝试了几种解决方法,但我们不喜欢在所有的控制器、指令甚至一些工厂中注入$rootScope。因此,到目前为止,$timeout和_.defer是我们最喜欢的方法。这些方法成功地告诉angular等待下一个动画循环,从而确保当前的scope.$apply已经结束。
underscore.js
的情况下,才应该使用此解决方案。 为了仅使用其defer
函数而导入整个underscore库是不值得的。 我更喜欢$timeout
的解决方案,因为每个人都可以通过angular访问$timeout
,而不依赖于其他库。 - tennisgent$timeout
既不是最佳解决方案,也不是正确的解决方案。同时,请确保阅读,如果您关心性能或可扩展性。$$phase
是框架私有的,这是有充分理由的。
$timeout(callback)
将等待当前$digest周期(如果有)完成,然后执行回调,最后在结束时运行一个完整的$apply
。
$timeout(callback, delay, false)
将执行相同的操作(在执行回调之前带有可选延迟),但不会触发$apply
(第三个参数),如果您没有修改Angular模型($scope),则可以节省性能。
$scope.$apply(callback)
调用,除其他外,$rootScope.$digest
,这意味着它将重新处理应用程序的根作用域及其所有子级,即使您在隔离作用域内也是如此。
$scope.$digest()
只需将其模型与视图同步,但不会处理其父作用域,这可以在使用隔离作用域(主要来自指令)的HTML的隔离部分上节省大量性能。$digest不接受回调:您执行代码,然后进行处理。
$scope.$evalAsync(callback)
是在angularjs 1.2中引入的,并且可能会解决大多数问题。请参阅最后一段以了解更多信息。
如果出现$digest already in progress error
,则说明您的架构有误:要么您不需要重新处理作用域,要么您不应该负责处理它(请参见下文)。
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);
});
$timeout
被轻描淡写地批评了一下。你能否提供更多避免使用$timeout
的原因? - mlhDev$scope.applyAsync()
怎么样?在这个问题的任何答案中都没有提到过它,但我在其他帖子中看到过。当我将所有的$scope.apply()
更改为scope.applyAsync()
时,我所有的$digest周期错误都消失了……不知道是否给自己带来了更多问题,但目前还没有问题。 - rolinger这是一个方便的小助手方法,可以使此过程保持DRY:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
scope.$apply(fn);
应该改为 scope.$apply(fn());
,因为 fn() 会执行函数而不是 fn。请帮我找出错误之处。 - madhu131313我也遇到了第三方脚本的问题,例如CodeMirror和Krpano,即使按照这里提到的使用safeApply方法,仍然无法解决错误。
但是,解决它的方法是使用$timeout服务(不要忘记首先注入它)。
因此,可以这样做:
$timeout(function() {
// run my code safely here
})
如果您的代码中使用了
this
可能是因为它位于工厂指令的控制器内部或者需要某种绑定,那么您可以这样做:
.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/$rootScope:inprog
当您调用$apply
时,可能会出现问题,因为该调用有时会在 Angular 代码之外异步运行(应使用 $apply),而有时会在 Angular 代码内同步运行(这会导致“$digest already in progress”错误)。
例如,当您拥有一个从服务器异步获取并缓存项目的库时,可能会发生这种情况。第一次请求项目时,将异步检索它以不阻止代码执行。但是,在第二次请求时,项目已经在缓存中,因此可以同步检索它。
避免此错误的方法是确保调用$apply
的代码是异步运行的。这可以通过在将延迟设置为0
(默认值)的$timeout
调用内运行代码来完成。然而,将您的代码放入$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...
只有在你知道运行代码时将始终在Angular代码之外运行时(例如,您对$apply的调用将发生在由Angular代码之外的代码调用的回调函数中)才调用$apply
。
除非有人意识到使用$timeout
会带来一些重大的不利影响,否则我认为您可以始终使用$timeout
(设置为零延迟)而不是$apply
,因为它们将做大致相同的事情。
$apply
但仍然出现错误。 - ariscris$apply
是同步的(它的回调函数被执行后,才会执行 $apply
后面的代码),而 $timeout
不是:当前 $timeout
后面的代码会被执行,然后一个新的堆栈开始运行其回调函数,就好像您使用了 setTimeout
。如果您更新了两次相同的模型,这可能会导致图形故障:$timeout
会等待视图被刷新后再次更新它。 - floribon$apply()
。如果您的视图未按预期更新,然后在调用$apply()
后出现此错误,这很可能意味着您没有正确更新模型。如果您发布一些具体信息,我们可以找出核心问题。$scope.err_message = 'err message';
不是正确的更新方式吗? - OZ_$apply()
。很容易陷入视图不正确的陷阱,于是你到处抛出一堆$apply()
,然后最终导致OP中看到的错误。当我说“你没有正确更新模型”时,我只是指所有业务逻辑都没有正确填充可能存在于作用域中的任何内容,这会导致视图不如预期地显示。 - dnc253最安全的$apply
的最简形式为:
$timeout(angular.noop)
你还可以使用evalAsync。它将在digest完成后的某个时候运行!
scope.evalAsync(function(scope){
//use the scope...
});
$timeout()
。 - Onur Yıldırımng-*
)内部调用scope.$apply函数。确保如果您在一个通过timeout/ajax/events调用的函数内部调用它,那么它不会在初始化加载时同时运行。 - Patrick