什么时候使用 $scope.$apply() 是安全的?
Posted
技术标签:
【中文标题】什么时候使用 $scope.$apply() 是安全的?【英文标题】:When is it safe to use $scope.$apply()? 【发布时间】:2015-07-01 06:21:14 【问题描述】:我想标题很清楚我在问什么。我创建了这个小提琴:http://jsfiddle.net/Sourabh_/HB7LU/13142/
在小提琴中,我尝试复制async
场景。这只是一个示例,但在 AJAX 调用中,如果我不使用 $scope.$apply()
,则列表不会更新。我想知道每次我进行 AJAX 调用以更新列表时使用 $scope.$apply()
是否安全,或者我可以使用其他一些机制吗?
我为复制场景而编写的代码(与小提琴相同):
HTML
<div ng-controller="MyCtrl">
<li ng-repeat="item in items">
item.name
</li>
<button ng-click="change()">Change</button>
</div>
JS
var myApp = angular.module('myApp',[]);
function MyCtrl($scope)
$scope.items = [name : "abc",name : "xyz",name : "cde"];
$scope.change = function()
test(function(testItem)
$scope.items = testItem;
//$scope.$apply();
)
function test(callback)
var testItem = [
name : "mno",
name : "pqr",
name : "ste"
];
setTimeout(function()callback(testItem),2000);
【问题讨论】:
您是否在尝试模仿 REST 调用?如果是这样: $http 请求返回一个承诺,您可以在控制器内部使用 .then() 来更改范围。不要在 REST 调用中设置新的范围和 $apply()。因为,你现在的所作所为毫无意义。 我知道它现在无济于事,但 Angular 2.0 将解决这个可怕的问题。 每次范围更改都贯穿摘要。 API 调用中的超时对我来说很臭。如果服务器需要更多时间怎么办?如果它被调用 2 或 3 次怎么办? @gruberb。那么在什么场景下我们使用apply(),有没有只有apply()才起作用的情况呢?或者换句话说,我们什么时候应该使用 apply()? 我认为当你觉得需要 apply() 时,可以做得更好。作为 $rootScope,$apply() 应该在极少数情况下使用。 github.com/angular/angular.js/wiki/… 【参考方案1】:编辑不清楚 OP 是否试图模拟后端调用。即便如此,使用 $timeout
服务是避免手动调用 $scope.$apply
的好方法,并且是比使用 Promise 更普遍适用的解决方案(如果你不调用 $http
它不会'通过用 Promise 包装它们来强制你的更改进入下一个周期总是有意义的)。
更新您的代码以使用$timeout service,它应该可以工作而无需调用
$apply
。
$timeout
是原生 setTimeout
的包装器,但有一个重要区别:$timeout
将至少延迟执行,直到下一个 $digest
循环运行。
所以没有延迟传递仍然会将执行延迟到下一个周期。传入 2000 会将执行延迟到 2000ms 后的下一个周期。
因此,这是一个简单的技巧,可以确保您的更改被 Angular 接收,而无需手动调用 $apply
(在任何情况下都被认为是不安全的)
function MyCtrl($scope, $timeout)
$scope.items = [name : "abc",name : "xyz",name : "cde"];
$scope.change = function()
test(function(testItem)
$scope.items = testItem;
//$scope.$apply();
)
function test(callback)
var testItem = [
name : "mno",
name : "pqr",
name : "ste"
];
$timeout(function()callback(testItem),2000);
【讨论】:
我认为这个人想要模仿 REST 调用,建议只使用带有 .success()、.then() 或 .error() 的 promise 而不是超时。跨度> 这在问题中并不明显。 OP 询问何时可以安全地使用 $apply,但从来没有,所以我提供了一个替代方案。使用 $q 当然也可以解决这个问题,但使用$timeout
是一种更简单的方法,可以将您的更改强制进入下一个周期。
@Anzeo。抱歉,如果我不清楚,但是“测试”功能只是模仿服务器,其中回调实际上是来自服务器的响应。 +1 替代方案。
如果你不使用 $http 进行异步调用,这个解决方案仍然比手动调用 $scope.$apply
更受欢迎【参考方案2】:
如果您想模拟 API-Rest-Call,请在您的 Controller
中使用返回的 promise
,而不是在 Rest-Call 中设置范围。
$http.get('uri')
.success(function(data)
$scope.items = data
);
避免使用$apply()
。 From the Angular GitHub Repo:
$scope.$apply()
应该尽可能靠近异步事件绑定发生 可能。不要在你的代码中随意使用它。如果你这样做
(!$scope.$$phase) $scope.$apply()
是因为你不够高 在调用堆栈中。
你的问题:
如果您发现自己需要 $apply(),请重新考虑您的结构。 出于安全原因:切勿使用$apply()
【讨论】:
使用 $apply() 并不安全是不正确的。我们不能 $apply()。无论如何,它都会在应用程序的生命周期中发生。在适当的时候使用和理解它是非常重要的。正确的是,尽可能不要调用 $apply(),即 $timeout 而不是 setTimeout,... 我能想到不使用 $apply() 的唯一原因是当您只想使用 $digest() 时。因为 digest 只是评估当前范围的变量,但 $apply() 最终调用 $rootScope.$digest() 重新计算所有绑定,这是非常昂贵的。但是,如果用例是这样,那么我认为使用 ti 没有什么问题【参考方案3】:每次使用非“角度方式”的东西时都需要使用 $apply,就像 Anzeo 所说的 $timeout。 例如,如果您使用 jQuery 的 http 而不是 angular 的 $http,则必须添加 $scope.$apply。
【讨论】:
【参考方案4】:$apply,应在代码未在角度摘要循环中执行时使用。在正常情况下,我们不需要使用它,但如果我们有一个从 jQuery 事件处理程序或像 setTimeout()
这样的方法调用的代码,我们可能必须使用它。即使您有一个从另一个角度函数(如 watch
或角度事件处理程序)调用的函数,您也不需要使用 $apply(),因为这些脚本是在摘要循环中执行的。
一种安全的方法是在调用$scope.$apply() 之前检查$scope.$$phase
参数
if($scope.$$phase)
$scope.$apply();
在您的情况下,您可以按照另一个答案中的建议使用 $timeout
What is $$phase in AngularJS? Why is using if(!$scope.$$phase) $scope.$apply() an anti-pattern?【讨论】:
【参考方案5】:以上所有答案都提供了一些信息,但他们没有回答我的一些疑问,或者至少我不明白。所以我给我自己的。
角度文档when to use $apply中非常清楚地说明了
$http 或 $timeout 或 ng-click, ng-..... 的回调包含 $apply() 。因此,当人们说你在做有角度的做事时不必使用 $apply() 时,就是这样。但是,其中一个答案提到角度事件处理程序也用 $apply() 包装。这不是真的,或者用户的意思只是 ng-click 类型的事件(同样是 ng-....)。如果事件在 $http 或 $timeout 或 ng-click 之外的 rootScope(或任何范围)上广播,例如:来自自定义服务,那么您需要在您的范围上使用 $apply() 尽管 $rootScope .$broadcast 也是有角度的做事方式。在大多数情况下,我们不需要这个,因为当事情发生时应用程序的状态会改变。即,点击,选择更改等......当我们分别使用 ng-click ng-change 时,这些在角度方面已经在使用 $apply() 。使用 signalr 或 socket.io 处理服务器端事件,并在编写自定义指令时,只需要更改指令的范围是少数几个使用 $apply() 非常重要的例子
【讨论】:
【参考方案6】:正如@gruberb 在 cmets 中指出的那样,如果您尝试模拟 REST 调用,最好使用 Promise,而不是 $apply
。
为此,您需要使用$q service 创建并返回一个promise。然后只需调用它并通过在返回的 Promise 上调用 then()
方法来处理您的结果。
function MyCtrl($scope, $q)
$scope.items = [name : "abc",name : "xyz",name : "cde"];
$scope.change = function()
test().then(function (items)
$scope.items = items;
$scope.$apply();
);
;
function test()
var defered = $q.defer();
var testItem = [
name : "mno",
name : "pqr",
name : "ste"
];
setTimeout(function()
defered.resolve(testItem);
,2000);
return defered.promise;
【讨论】:
【参考方案7】:更好的方法是使用$scope.$applyAsync();
而不是$scope.$apply();
这里给出了避免使用 $scope.$apply() 的原因:
Error: [$rootScope:inprog] digest in progress. Fix
【讨论】:
一个理由如果写在这里会更有用。链接有时会停止响应。以上是关于什么时候使用 $scope.$apply() 是安全的?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 AngularJS 中使用 $scope.$watch 和 $scope.$apply?
AngularJS:$ scope.array.push()不会更新视图,即使使用$ apply也是如此