什么时候使用$ scope是安全的。$ apply()?

时间:2015-04-23 07:55:43

标签: javascript ajax angularjs

我想这个标题非常清楚我要问的是什么。我创造了这个小提琴: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);
  }
}

7 个答案:

答案 0 :(得分:6)

编辑目前尚不清楚OP正在尝试模拟后端通话。即便如此,使用$timeout服务是一种避免手动调用$scope.$apply的好方法,也是一种比使用Promise更常用的解决方案(如果你没有调用{{ 1}}通过用Promise包装它们来强制你的更改进入下一个循环并不总是有意义的。

<小时/> 更新您的代码以使用$timeout service,无需致电$http即可使用。

$apply是原生$timeout的包装器,具有重要区别:setTimeout将至少延迟执行,直到下一个$timeout周期运行。

因此,在没有延迟的情况下,仍然可以将执行延迟到下一个周期。 2000年通过将延迟执行到2000ms后的下一个周期。

因此,这是一个简单的技巧,可以确保您的更改被Angular选中而无需手动调用$digest(在任何情况下都被视为不安全)

$apply

答案 1 :(得分:5)

如果您要移植API-Rest-Call,请使用promise中返回的Controller,而不是在Rest-Call中设置范围。

$http.get('uri')
  .success(function(data) {
    $scope.items = data
});

避免使用$apply()From the Angular GitHub Repo

  

$scope.$apply()应该尽可能接近async事件绑定   可能的。

     

请勿在整个代码中随意播放。如果你这样做的话   (!$scope.$$phase) $scope.$apply()这是因为你不够高   在调用堆栈中。

问题:

  • 如果您发现自己需要$ apply(),请重新考虑您的结构。
  • 出于安全原因:切勿使用$apply()

答案 2 :(得分:3)

当在角度摘要循环中未执行代码时,应使用$apply。在正常情况下,我们不需要使用它,但如果我们有一个从jQuery事件处理程序或setTimeout()之类的方法调用的代码,我们可能必须使用它。即使你有一个从另一个角度函数调用的函数,如watch或角度事件处理程序,你也不需要使用$ apply(),因为这些脚本在摘要周期中执行。

一种安全的方法是在调用$scope.$apply()之前检查$scope.$$phase参数

if($scope.$$phase){
    $scope.$apply();
}

在您的情况下,您可以使用另一个答案中建议的$ timeout

答案 3 :(得分:2)

每次使用非“角度方式”的东西时,你需要使用$ apply,就像Anzeo讲述$ timeout一样。
例如,如果您使用jQuery的http而不是angular的$ http,则必须添加$ scope。$ apply。

答案 4 :(得分:0)

正如@gruberb在评论中指出的,如果你试图模拟一个REST调用,你最好使用一个承诺而不是$apply

为此,您需要使用$q service来创建并返回一个承诺。然后只需调用它并通过在返回的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;
    }
}

答案 5 :(得分:0)

以上所有答案都提供了一些信息,但他们没有回答我的疑问,或者至少我不理解。所以我给自己的。

在角度文档when to use $apply

中非常清楚地说明了这一点

$ http或$ timeout或ng-click,ng -.....的回调函数包含$ apply()。因此,当人们说你不做必须使用$ apply()时,你采取棱角分明的做事方式,就是这样。但是,其中一个提到角度事件处理程序的答案也包含在$ apply()中。这不是真的,或者用户意味着只需点击一种事件(再次ng -....)。如果事件在$ http或$ timeout或ng-click之外的rootScope(或任何范围)上广播,例如:来自自定义服务,那么你需要在你的范围内使用$ apply()尽管$ rootScope 。$ broadcast也是有角度的做事方式。在大多数情况下,我们不需要这个,因为应用程序的状态会在发生某些事情时发生变化。即,点击,选择更改等...当我们分别使用ng-click ng-change时,这些是有角度的已经使用$ apply()。使用signalr或socket.io处理服务器端事件,并在编写自定义指令时,只需要更改指令的范围,这是使用$ apply()

非常必要的几个例子。

答案 6 :(得分:0)

更好的方法是使用$scope.$applyAsync();代替$scope.$apply();

避免使用$ scope。$ apply()的原因如下:

Error: [$rootScope:inprog] digest in progress. Fix