我发现自从以角度构建应用程序以来,我需要越来越多地手动将页面更新到我的范围。
我唯一知道的方法是$apply()
从控制器和指令的范围进行调用。问题是它不断向显示以下内容的控制台抛出错误:
错误:$ digest已经在进行中
有谁知道如何避免这种错误或以不同的方式实现相同的目的?
$timeout()
ng-*
)。确保,如果你是在一个函数中调用它(即通过超时/ AJAX /事件调用),它没有也正在对负荷运行开始。
我发现自从以角度构建应用程序以来,我需要越来越多地手动将页面更新到我的范围。
我唯一知道的方法是$apply()
从控制器和指令的范围进行调用。问题是它不断向显示以下内容的控制台抛出错误:
错误:$ digest已经在进行中
有谁知道如何避免这种错误或以不同的方式实现相同的目的?
$timeout()
ng-*
)。确保,如果你是在一个函数中调用它(即通过超时/ AJAX /事件调用),它没有也正在对负荷运行开始。
Answers:
不要使用此模式 -最终将导致更多错误,无法解决。即使您认为它可以修复某些问题,也没有。
您可以$digest
通过检查来检查a 是否已经在进行中$scope.$$phase
。
if(!$scope.$$phase) {
//$digest or $apply
}
$scope.$$phase
将返回,"$digest"
或者"$apply"
如果正在进行$digest
或$apply
。我相信这些状态之间的区别在于,$digest
它将处理当前范围及其子项$apply
的监视,并将处理所有范围的监视者。
就@ dnc253而言,如果您发现自己打电话$digest
或$apply
经常打电话,则可能是做错了。我通常发现,由于Ang事件无法触发DOM事件而需要更新作用域的状态时,我通常需要进行摘要。例如,当一个Twitter引导模式被隐藏时。有时DOM事件在a $digest
进行时触发,有时不触发。这就是为什么我使用这张支票。
如果有人知道,我想知道一种更好的方法。
来自评论:@anddoutoi
- 不这样做
if (!$scope.$$phase) $scope.$apply()
,这意味着您$scope.$apply()
在调用堆栈中不够高。
if (!$scope.$$phase) $scope.$apply()
”,github.com
最近与Angular员工就此主题进行了讨论:出于面向未来的原因,不应使用$$phase
当按“正确”的方式操作时,当前答案是
$timeout(function() {
// anything you want can go here and will safely be run on the next digest.
})
我最近在编写有角度的服务来包装Facebook,Google和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的delay参数是可选的,如果未设置则默认为0($ timeout调用$ browser.defer,如果未设置delay则默认为0)。
有点非直觉,但这就是编写Angular的人的答案,所以对我来说足够好了!
$timeout
而不是本机setTimeout
,为什么不使用$window
本机window
?
$timeout
在这种情况下,使用的目的是$timeout
确保正确更新角度范围。如果没有$ digest,则将导致运行新的$ digest。
cancel
。来自文档:“因此,诺言将被拒绝解决。” 您无法解决已解决的承诺。您的取消不会引起任何错误,但也不会做任何积极的事情。
摘要周期是一个同步调用。在完成之前,它不会对浏览器的事件循环产生控制权。有几种方法可以解决此问题。解决此问题的最简单方法是使用内置的$ timeout,第二种方法是如果使用下划线或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
功能就不值得导入整个下划线库。我更喜欢该$timeout
解决方案,因为每个人都已经可以$timeout
通过angular 访问,而无需依赖其他库。
这里的许多答案都包含很好的建议,但也可能导致混乱。只需用$timeout
是不是最好的,也不是正确的解决方案。另外,如果您对性能或可伸缩性感到担心,请务必阅读。
$$phase
该框架是私有的,并且有充分的理由。
$timeout(callback)
将等待直到当前摘要周期(如果有)完成,然后执行回调,然后在最后运行full $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);
});
而且,如果您知道自己在做什么并且在大型Angular应用程序的一部分中使用一个孤立的小指令,则可以选择$ digest而不是$ apply来节省性能。
新的功能强大的方法已添加到任何$ scope:中$evalAsync
。基本上,如果发生,它将在当前摘要周期内执行其回调,否则,一个新的摘要周期将开始执行该回调。
$scope.$digest
如果您真的只知道同步HTML的孤立部分(因为$apply
如果没有正在进行的情况下会触发新的同步),那仍然不如您想的那样,但这是执行函数时的最佳解决方案。这你可以不知道它是否会同步或不执行获取潜在缓存的资源之后,例如:有时这需要一个异步调用到服务器,否则,资源将在本地获取同步。
在这些情况下,以及所有其他您拥有的情况下!$scope.$$phase
,请务必使用$scope.$evalAsync( callback )
$timeout
被批评通过。您能给出更多避免的理由$timeout
吗?
方便的小帮手方法,可保持此过程干燥:
function safeApply(scope, fn) {
(scope.$$phase || scope.$root.$$phase) ? fn() : scope.$apply(fn);
}
scope.$apply(fn);
应该是scope.$apply(fn());
因为fn()将执行函数而不是fn。请帮助我到哪里错了
我在使用第三方脚本(例如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/$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
over 有一些明显的不利影响,否则$apply
我不明白为什么不能总是使用$timeout
(零延迟)代替$apply
,因为它会做大致相同的事情。
$apply
自己但仍然出现错误的情况很有用。
$apply
同步(执行其回调,然后执行$ apply之后的代码),$timeout
而不同步:执行超时后的当前代码,然后一个新的堆栈以其回调开始,就像您正在使用一样setTimeout
。如果您对同一模型进行两次更新,则可能会导致图形故障:$timeout
将在刷新视图之前等待视图刷新。
当您收到此错误时,基本上意味着它已经在更新视图。您确实不需要$apply()
在控制器内调用。如果您的视图未按预期更新,然后在调用时收到此错误$apply()
,则很可能表示您没有正确更新模型。如果您发布一些细节,我们可以找出核心问题。
you're not updating the the model correctly
什么?$scope.err_message = 'err message';
是不正确的更新?
$apply()
是在更新angular的“外部”模型时(例如,从jQuery插件获取)。很容易陷入视图不正确的陷阱中,因此您$apply()
到处都丢了一堆s,最后导致在OP中看到错误。当我说的时候,you're not updating the the model correctly
我只是说所有业务逻辑都没有正确填充范围内的任何内容,这导致视图看起来不符合预期。
首先,不要这样解决
if ( ! $scope.$$phase) {
$scope.$apply();
}
这是没有道理的,因为$ phase只是$ digest循环的布尔标志,因此$ apply()有时不会运行。请记住,这是一个不好的做法。
相反,使用 $timeout
$timeout(function(){
// Any code in here will automatically have an $scope.apply() run afterwards
$scope.myvar = newValue;
// And it just works!
});
如果使用下划线或破折号,则可以使用defer():
_.defer(function(){
$scope.$apply();
});
有时,如果使用这种方式,您仍然会出错(https://stackoverflow.com/a/12859093/801426)。
尝试这个:
if(! $rootScope.$root.$$phase) {
...
$rootScope
而且anyScope.$root
是同一个人。$rootScope.$root
是多余的。
您应根据上下文使用$ evalAsync或$ timeout。
这是一个带有很好解释的链接:
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
尝试使用
$scope.applyAsync(function() {
// your code
});
代替
if(!$scope.$$phase) {
//$digest or $apply
}
$ applyAsync安排$ apply的调用在以后发生。这可用于排队多个需要在同一摘要中求值的表达式。
注意:在$ digest中,仅当当前范围为$ rootScope时,才会刷新$ applyAsync()。这意味着,如果在子作用域上调用$ digest,则不会隐式刷新$ applyAsync()队列。
范例:
$scope.$applyAsync(function () {
if (!authService.authenticated) {
return;
}
if (vm.file !== null) {
loadService.setState(SignWizardStates.SIGN);
} else {
loadService.setState(SignWizardStates.UPLOAD_FILE);
}
});
参考文献:
我建议您使用自定义事件,而不要触发摘要周期。
我发现,广播自定义事件并为此事件注册侦听器是触发您希望发生的操作的好方法,无论您是否处于摘要循环中。
通过创建自定义事件,您还可以提高代码效率,因为您仅触发订阅该事件的侦听器,而不像调用scope。$ apply那样触发绑定到范围的所有监视。
$scope.$on('customEventName', function (optionalCustomEventArguments) {
//TODO: Respond to event
});
$scope.$broadcast('customEventName', optionalCustomEventArguments);
yearofmoo在为我们创建可重用的$ 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);
通过调用$eval
而不是$apply
在我知道该$digest
函数将运行的地方,我已经能够解决此问题。
根据docs,$apply
基本上是这样做的:
function $apply(expr) {
try {
return $eval(expr);
} catch (e) {
$exceptionHandler(e);
} finally {
$root.$digest();
}
}
就我而言,ng-click
更改范围内的变量,对该变量的$ watch更改必须为的其他变量$applied
。最后一步导致错误“消化已在进行中”。
通过在watch表达式内部替换$apply
,$eval
范围变量将按预期更新。
因此,似乎由于Angular中的某些其他更改而使摘要始终无法运行时,$eval
只需执行ing操作。
用$scope.$$phase || $scope.$apply();
代替
理解的是,角文件调用检查$$phase
的反模式,我就先$timeout
和_.defer
工作。
超时和延迟方法{{myVar}}
在dom中像FOUT一样产生了无法解析的内容。对我来说,这是不可接受的。这让我没有太多的教条式地告诉我某事是hack,并且没有合适的替代方法。
每次唯一起作用的是:
if(scope.$$phase !== '$digest'){ scope.$digest() }
。
我不了解这种方法的危险性,也无法理解为什么人们在评论和有角度的团队中将其描述为骇客行为。该命令似乎精确且易于阅读:
“进行摘要,除非已经发生”
在CoffeeScript中,它甚至更漂亮:
scope.$digest() unless scope.$$phase is '$digest'
这是什么问题?是否有不会创建FOUT的替代方案?$ safeApply看起来不错,但也使用$$phase
检查方法。
这是我的效用服务:
angular.module('myApp', []).service('Utils', function Utils($timeout) {
var Super = this;
this.doWhenReady = function(scope, callback, args) {
if(!scope.$$phase) {
if (args instanceof Array)
callback.apply(scope, Array.prototype.slice.call(args))
else
callback();
}
else {
$timeout(function() {
Super.doWhenReady(scope, callback, args);
}, 250);
}
};
});
这是一个用法示例:
angular.module('myApp').controller('MyCtrl', function ($scope, Utils) {
$scope.foo = function() {
// some code here . . .
};
Utils.doWhenReady($scope, $scope.foo);
$scope.fooWithParams = function(p1, p2) {
// some code here . . .
};
Utils.doWhenReady($scope, $scope.fooWithParams, ['value1', 'value2']);
};
我一直在使用这种方法,它似乎工作得很好。这只是等待周期结束的时间,然后触发apply()
。只需apply(<your scope>)
从所需的任何位置调用该函数。
function apply(scope) {
if (!scope.$$phase && !scope.$root.$$phase) {
scope.$apply();
console.log("Scope Apply Done !!");
}
else {
console.log("Scheduling Apply after 200ms digest cycle already in progress");
setTimeout(function() {
apply(scope)
}, 200);
}
}
您可以使用
$timeout
以防止错误。
$timeout(function () {
var scope = angular.element($("#myController")).scope();
scope.myMethod();
scope.$scope();
},1);
当我们要求有角度地运行摘要循环时,这个问题基本上就来了,尽管它的过程正在引起人们对角度的理解。控制台中的结果异常。
1.在$ timeout函数中调用scope。$ apply()没有任何意义,因为在内部它是相同的。
2.该代码具有香草JavaScript函数,因为它的本机不是按角度定义的,即setTimeout。3
.为此,您可以使用
if(!scope。$$ phase){
scope。$ evalAsync(function(){
}); }
let $timeoutPromise = null;
$timeout.cancel($timeoutPromise);
$timeoutPromise = $timeout(() => {
$scope.$digest();
}, 0, false);
这是避免此错误并避免$ apply的好方法
如果基于外部事件调用,则可以将其与debounce(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();
});
发现了这一点:https : //coderwall.com/p/ngisma,其中Nathan Walker(在页面底部)建议在$ rootScope中使用装饰器来创建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;
}
]);
}
]);