有时我需要在我的代码中使用 $scope.$apply
,有时它会抛出“digest already in progress”错误。所以我开始想办法解决这个问题,发现了这个问题:AngularJS : Prevent error $digest already in progress when calling $scope.$apply()。然而,在评论(和角度维基)中,您可以阅读:
不要这样做 if (!$scope.$$phase) $scope.$apply(),这意味着你的 $scope.$apply() 在调用堆栈中不够高。
所以现在我有两个问题:
为什么这是一个反模式?我怎样才能安全地使用 $scope.$apply?
另一个防止“正在消化”错误的“解决方案”似乎是使用 $timeout:
$timeout(function() {
//...
});
这是要走的路吗?它更安全吗?所以这是真正的问题:我如何才能完全消除“正在消化”错误的可能性?
PS:我只在非同步的非 angularjs 回调中使用 $scope.$apply。 (据我所知,如果您希望应用更改,则必须使用 $scope.$apply 的情况)
scope
。所以根据这个你总是知道,如果你需要调用 scope.$apply
与否。如果您对角度/非角度 scope
操作使用相同的代码,那么您做错了,它应该始终分开......所以基本上如果您遇到需要检查 {4 },您的代码没有以正确的方式设计,并且总有一种方法可以“以正确的方式”进行
digest already in progress
错误
经过更多的挖掘,我能够解决使用 $scope.$apply
是否总是安全的问题。简短的回答是肯定的。
长答案:
由于您的浏览器执行 Javascript 的方式,两个摘要调用不可能偶然发生冲突。
我们编写的 JavaScript 代码并不是一次性运行,而是轮流执行。这些回合中的每一个都从头到尾不间断地运行,当一个回合正在运行时,我们的浏览器中不会发生任何其他事情。 (来自 http://jimhoskins.com/2012/12/17/angularjs-and-apply.html)
因此,错误“digest already in progress”只能在一种情况下发生:当在另一个 $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);
});
什么是 不 安全的(因为 $timeout - 像所有 angularjs 助手一样 - 已经为您调用 $scope.$apply
):
$timeout(function () {
$scope.$apply(function () {
$scope.message = "Timeout called!";
});
}, 2000);
这也解释了为什么使用 if (!$scope.$$phase) $scope.$apply()
是一种反模式。如果您以正确的方式使用 $scope.$apply
,则根本不需要它:例如,在像 setTimeout
这样的纯 js 回调中。
阅读 http://jimhoskins.com/2012/12/17/angularjs-and-apply.html 以获得更详细的说明。
现在它绝对是一种反模式。即使您检查 $$phase,我也看到摘要爆炸了。您只是不应该访问由 $$
前缀表示的内部 API。
你应该使用
$scope.$evalAsync();
因为这是 Angular ^1.4 中的首选方法,并且专门作为应用程序层的 API 公开。
在任何情况下,当您的摘要正在进行中并且您推送另一个服务进行摘要时,它只会给出一个错误,即摘要已经在进行中。所以要解决这个问题,你有两个选择。您可以检查正在进行的任何其他摘要,例如轮询。
第一
if ($scope.$root.$$phase != '$apply' && $scope.$root.$$phase != '$digest') {
$scope.$apply();
}
如果上述条件为真,那么您可以应用您的 $scope.$apply 否则
第二种解决方案是使用 $timeout
$timeout(function() {
//...
})
它不会让其他摘要开始,直到 $timeout 完成它的执行。
$scope.$apply();
,请参阅 @gaul 的出色回答。
$timeout
是关键!它有效,后来我发现它也被推荐。
scope.$apply
触发 $digest
循环,这是 2 路数据绑定的基础
$digest
循环检查附加到 $scope
的对象,即模型(准确地说是 $watch
),以评估它们的值是否已更改,如果检测到更改,则需要采取必要的步骤来更新视图。
现在,当您使用 $scope.$apply
时,您会遇到“已经在进行中”的错误所以很明显 $digest 正在运行,但是什么触发了它?
答案-->每个 $http
调用、所有 ng-click、重复、显示、隐藏等都会触发一个 $digest
循环,并且它在每个 $SCOPE 中运行最糟糕的部分。
即说您的页面有 4 个控制器或指令 A、B、C、D
如果每个属性中有 4 个 $scope
属性,那么您的页面上总共有 16 个 $scope 属性。
如果您在控制器 D 中触发 $scope.$apply
,则 $digest
循环将检查所有 16 个值!!!加上所有 $rootScope 属性。
Answer-->但 $scope.$digest
会在子级和相同范围内触发 $digest
,因此它只会检查 4 个属性。因此,如果您确定 D 中的更改不会影响 A、B、C,则使用 $scope.$diges
t 而不是 $scope.$apply
。
因此,即使用户未触发任何事件,仅 ng-click 或 ng-show/hide 就可能触发超过 100 多个属性的 $digest
循环!
使用$timeout
,这是推荐的方式。
我的场景是我需要根据从 WebSocket 收到的数据更改页面上的项目。而且由于它在 Angular 之外,没有 $timeout,唯一的模型将被更改,但视图不会被更改。因为 Angular 不知道那条数据已被更改。 $timeout
基本上是在告诉 Angular 在下一轮 $digest 中做出改变。
我也尝试了以下方法并且它有效。对我来说不同的是 $timeout 更清晰。
setTimeout(function(){
$scope.$apply(function(){
// changes
});
},0)
$http
)。否则,您必须在所有地方重复此代码。
setTimeout
或 $timeout
,则不需要 $scope.$apply
我找到了非常酷的解决方案:
.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
});
}
])
$document.bind('keydown', function(e) { $rootScope.$apply(function() { // a passed through function from the controller gets executed here }); });
创建服务我真的不知道为什么我必须在这里制作 $apply,因为我正在使用 $document.bind..function $DocumentProvider(){ this.$get = ['$window', function(window){ return jqLite(window.document); }]; }
那里没有申请。$timeout
在语义上意味着延迟后运行代码。这可能是一个功能安全的事情,但它是一个黑客。当您无法知道$digest
循环是否正在进行或您已经在$apply
中时,应该有一种安全的方式来使用 $apply。