Javascript 什么时候使用 $scope.$apply() 是安全的?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29817111/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
When is it safe to use $scope.$apply()?
提问by Zee
I guess the title is pretty much clear what I am asking. I have created this fiddle : http://jsfiddle.net/Sourabh_/HB7LU/13142/
我想标题很清楚我在问什么。我创建了这个小提琴:http: //jsfiddle.net/Sourabh_/HB7LU/13142/
In the fiddle I have tried to replicate an asyncscenario. This is just an example but in an AJAX call if I don't use $scope.$apply()the list does not get updated. I want to know if it is safe to use $scope.$apply()every time I make an AJAX call to update a list or is there some other mechanism I can make use of?
在小提琴中,我试图复制一个async场景。这只是一个示例,但在 AJAX 调用中,如果我不使用$scope.$apply()列表,则不会更新。我想知道$scope.$apply()每次我进行 AJAX 调用来更新列表时使用它是否安全,或者是否有其他一些我可以使用的机制?
Code I have written to replicate the scenario(same as in fiddle):
我为复制场景而编写的代码(与小提琴相同):
HTML
HTML
<div ng-controller="MyCtrl">
<li ng-repeat="item in items">
{{item.name}}
</li>
<button ng-click="change()">Change</button>
</div>
JS
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);
}
}
采纳答案by Bastian Gruber
If you want to immidate an API-Rest-Call, use the returned promisein your Controllerinstead setting the scope inside the Rest-Call.
如果您想模仿 API-Rest-Call,请promise在您Controller的 Rest-Call 中设置范围内的返回值。
$http.get('uri')
.success(function(data) {
$scope.items = data
});
Avoid using $apply(). From the Angular GitHub Repo:
避免使用$apply(). 来自 Angular GitHub 存储库:
$scope.$apply()should occur as close to the async event binding as possible.Do NOT randomly sprinkle it throughout your code.If you are doing if
(!$scope.$$phase) $scope.$apply()it's because you are not high enough in the call stack.
$scope.$apply()应该尽可能靠近异步事件绑定发生。不要在你的代码中随意地撒上它。如果你正在这样做,
(!$scope.$$phase) $scope.$apply()那是因为你在调用堆栈中不够高。
To your question:
对于你的问题:
- If you find yourself in a situation where you need $apply(), rethink your structure.
- Just for safety reason: Never use
$apply()
- 如果您发现自己处于需要 $apply() 的情况,请重新考虑您的结构。
- 仅出于安全原因:切勿使用
$apply()
回答by thomaux
EditIt was not clear the OP was trying to mock a backend call. Even so, using the $timeoutservice is a great way to avoid the need of calling $scope.$applymanually and is a more generally applicable solution than using a Promise (in cases where you're i.e. not calling $httpit doesn't always make sense to force your changes into the next cycle by wrapping them with a Promise).
编辑不清楚 OP 是否试图模拟后端调用。即便如此,使用该$timeout服务是避免$scope.$apply手动调用需求的好方法,并且是比使用 Promise 更普遍适用的解决方案(在您不调用$http它的情况下,强制您的更改并不总是有意义的通过用 Promise 包装它们来进入下一个循环)。
更新您的代码以使用 $timeout service$timeout 服务,它应该可以工作而无需调用
$apply$apply.$timeoutis a wrapper around the native setTimeoutwith an important difference: $timeoutwill delay the execution at least until the next $digestcycle runs.
$timeout是一个围绕本机的包装器,setTimeout有一个重要的区别:$timeout将延迟执行至少直到下一个$digest循环运行。
So passing in no delay will still delay the execution up until the next cycle. Passing in 2000 will delay the execution up to the next cycle after 2000ms.
所以没有延迟传递仍然会延迟执行直到下一个循环。传入 2000 将延迟执行到 2000ms 后的下一个周期。
Hence, this is an easy trick to make sure your changes are picked up by Angular without ever having to call $applymanually (which is considered unsafe in any case)
因此,这是一个简单的技巧,可以确保您的更改被 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);
}
}
回答by Ivan Toncev
You need to use $apply every time you use something that is not "angular way", like Anzeo told about $timeout.
For example if you use jQuery's http instead of angular's $http, you will have to add $scope.$apply.
每次使用非“角度方式”的东西时都需要使用 $apply,就像 Anzeo 所说的 $timeout 一样。
例如,如果您使用 jQuery 的 http 而不是 angular 的 $http,则必须添加 $scope.$apply。
回答by Arun P Johny
The $apply, should be used when the code is not executed in a angular digest loop. In normal circumstances we will not need to use it, but we might have to use it if we have a code that is called from a jQuery event handler or from methods like setTimeout(). Even if you have a function that is called from another angular function like a watchor angular event handlers you need not use $apply() as those scripts are executed in the digest cycle.
的$申请,当在一个角度不执行代码消化循环应该被使用。在正常情况下,我们不需要使用它,但如果我们有从 jQuery 事件处理程序或从setTimeout(). 即使您有一个从另一个角度函数(如 awatch或 angular 事件处理程序)调用的函数,您也不需要使用 $apply() 因为这些脚本是在摘要循环中执行的。
One safe way is to check the $scope.$$phaseparam before calling $scope.$apply()like
一种安全的方法是$scope.$$phase在调用$scope.$apply()之前检查参数,例如
if($scope.$$phase){
$scope.$apply();
}
In your case but you can use $timeout as suggested in the another answer
在您的情况下,但您可以按照另一个答案中的建议使用 $timeout
回答by Blackus
As @gruberb pointed out in the comments, if you tried to mock a REST call, you better use a promise than $apply.
正如@gruberb 在评论中指出的那样,如果您尝试模拟 REST 调用,则最好使用 promise 而非$apply.
For this you need to use $q serviceto create and return a promise. Then simply call it and work with you result by calling then()method on the returned promise.
为此,您需要使用$q 服务来创建和返回承诺。然后只需调用它并通过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;
}
}
回答by Srivathsa Harish Venkataramana
All the above answers give some information but they did not answer few doubts I had or at the least I did not understand. So I'm giving my own.
以上所有答案都提供了一些信息,但它们并没有回答我的一些疑问,或者至少我不明白。所以我给我自己的。
It is very clearly stated in angular docs when to use $apply
angular 文档中非常清楚地说明何时使用 $apply
the call backs of $http or $timeout or ng-click, ng-..... have $apply() wrapped in them. Therefore when people say you don't have to use $apply() when you do angular way of doing things, this is it. However, one of the answers mentions angular event handlers are also wrapped with $apply(). This is not true or by that the user means just ng-click kind of events (again ng-....). If the event is broadcast on rootScope (or any scope for that matter) outside of $http or $timeout or ng-click, for eg: from a custom service, then you need to use $apply() on your scope although $rootScope.$broadcast is also angular way of doing things. in most of the scenarios we will not need this because the state of the app changes when something happens. ie, clicks, selection change, etc... and these are in angular terms are already using $apply() when we use ng-click ng-change respectively. Handling server side events using signalr or socket.io and while writing custom directives where the necessity to change just the directive's scope are few examples where it is very essential to use $apply()
$http 或 $timeout 或 ng-click、ng-..... 的回调包含 $apply() 。因此,当人们说您在做有角度的事情时不必使用 $apply() 时,就是这样。但是,其中一个答案提到 angular 事件处理程序也用 $apply() 包装。这不是真的,或者用户意味着只是 ng-click 类型的事件(再次 ng-....)。如果事件在 $http 或 $timeout 或 ng-click 之外的 rootScope(或任何与此相关的范围)上广播,例如:来自自定义服务,那么您需要在您的范围上使用 $apply() 尽管 $rootScope .$broadcast 也是有角度的做事方式。在大多数情况下,我们不需要它,因为当发生某些事情时应用程序的状态会发生变化。即,点击、选择更改等... 当我们分别使用 ng-click ng-change 时,这些在角度上已经使用 $apply() 。使用 signalr 或 socket.io 处理服务器端事件,并在编写自定义指令时,只需要更改指令的范围是很少使用 $apply() 非常重要的示例
回答by MuhammadUmarFarooq
A better way is to use $scope.$applyAsync();instead of $scope.$apply();
更好的方法是使用$scope.$applyAsync();而不是$scope.$apply();
The reason to avoid use of $scope.$apply() is given here:
避免使用 $scope.$apply() 的原因如下:

