有时候我需要在我的代码中使用$scope.$apply
,有时它会抛出一个已经在进行中的摘要"错误。所以我开始找到解决这个问题的方法并找到了这个问题:AngularJS : Prevent error $digest already in progress when calling $scope.$apply()。但是在评论中(以及角度维基上),您可以阅读:
不要做(!$ scope。$$阶段)$ scope。$ apply(),这意味着你的$ scope。$ apply()在调用堆栈中不够高。< / p>
所以现在我有两个问题:
另一个&#34;解决方案&#34;防止&#34;消化已在进行中#34;错误似乎是使用$ timeout:
$timeout(function() {
//...
});
这是要走的路吗?它更安全吗?所以这是一个真正的问题:如何完全消除已经在进行中的消化的可能性&#34;错误?
PS:我只使用$ scope。$ apply适用于非同步的非angularjs回调。 (据我所知,你必须使用$ scope。如果你想要应用你的更改,则适用$)
答案 0 :(得分:109)
经过多次挖掘后,我能够解决使用$scope.$apply
是否始终安全的问题。简短的回答是肯定的。
答案很长:
由于您的浏览器执行Javascript的方式,两个摘要调用不可能偶然碰撞 。
我们编写的JavaScript代码不是一次性运行,而是轮流执行。这些转弯中的每一个都从头到尾不间断地运行,当转弯运行时,我们的浏览器中没有其他任何事情发生。 (来自http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)
因此错误&#34;摘要已在进行中&#34;只能在一种情况下发生:当在另一个$ apply中发出$ apply时,例如:
$scope.apply(function() {
// some code...
$scope.apply(function() { ... });
});
这种情况可以不出现如果我们在纯非angularjs回调中使用$ scope.apply,例如setTimeout
的回调。因此,以下代码是100%防弹,并且没有需要执行if (!$scope.$$phase) $scope.$apply()
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
即使这个也是安全的:
$scope.$apply(function () {
setTimeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
});
什么是 NOT 安全(因为$ timeout - 就像所有angularjs助手一样 - 已经为你调用$scope.$apply
):
$timeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
这也解释了为什么if (!$scope.$$phase) $scope.$apply()
的使用是一种反模式。如果您以正确的方式使用$scope.$apply
,则根本不需要它:例如,在{j}回调中,例如setTimeout
。
阅读http://jimhoskins.com/2012/12/17/angularjs-and-apply.html以获取更详细的说明。
答案 1 :(得分:16)
现在绝对是一种反模式。即使你检查了$$阶段,我也看到了消化的爆炸。您不应该访问由$$
前缀表示的内部API。
你应该使用
$scope.$evalAsync();
因为这是Angular ^ 1.4中的首选方法,并且特别是作为应用程序层的API公开。
答案 2 :(得分:9)
scope.$apply
触发$digest
周期,这是双向数据绑定的基础
$digest
周期检查对象,即附加到$watch
的模型(准确$scope
),以评估其值是否已更改,如果检测到更改,则需要采取必要的步骤更新视图。
现在当你使用$scope.$apply
时,你会遇到一个错误“已经在进行中”所以很明显$ digest正在运行但触发它的是什么?
ANS - &GT;每次$http
次呼叫,所有点击,重复,显示,隐藏等都会触发$digest
周期以及每个$ SCOPE的最差部分。
即说你的页面有4个控制器或指令A,B,C,D
如果每个属性中有4个$scope
属性,则页面上总共有16个$ scope属性。
如果您在控制器D中触发$scope.$apply
,则$digest
周期将检查所有16个值!加上所有$ rootScope属性。
答案 - &gt; 但是$scope.$digest
在子级和相同范围内触发$digest
,因此它只会检查4个属性。因此,如果您确定D中的更改不会影响A,B,C,那么请使用$scope.$diges
t而不是$scope.$apply
。
即使用户没有触发任何事件,仅仅点击或点击显示/隐藏也可能会触发超过100个属性的$digest
周期!
答案 3 :(得分:9)
在任何情况下,当您的摘要正在进行并且您推送其他服务进行摘要时,它只会出现错误,即摘要已在进行中。 所以要治愈这个你有两个选择。 您可以检查正在进行的其他摘要,例如民意调查。
第一个
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
$scope.$apply();
}
如果上述条件为真,那么您可以应用$ scope。$ apply otherwies not和
第二个解决方案是使用$ timeout
$timeout(function() {
//...
})
它不会让其他摘要开始直到$ timeout完成它的执行。
答案 4 :(得分:0)
使用$timeout
,这是建议的方式。
我的方案是我需要根据从WebSocket收到的数据更改页面上的项目。并且由于它在Angular之外,没有$ timeout,唯一的模型将被更改,但不会更改视图。因为Angular不知道那条数据已被更改。 $timeout
基本上告诉Angular在下一轮$ digest中进行更改。
我也尝试了以下内容并且有效。与我不同的是,$ timeout更清晰。
setTimeout(function(){
$scope.$apply(function(){
// changes
});
},0)
答案 5 :(得分:-1)
我找到了非常酷的解决方案:
.factory('safeApply', [function($rootScope) {
return function($scope, fn) {
var phase = $scope.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn) {
$scope.$eval(fn);
}
} else {
if (fn) {
$scope.$apply(fn);
} else {
$scope.$apply();
}
}
}
}])
在您需要的地方注入:
.controller('MyCtrl', ['$scope', 'safeApply',
function($scope, safeApply) {
safeApply($scope); // no function passed in
safeApply($scope, function() { // passing a function in
});
}
])