Javascript 何时使用$scope.$apply()是安全的?

Javascript 何时使用$scope.$apply()是安全的?,javascript,ajax,angularjs,Javascript,Ajax,Angularjs,我想标题很清楚我在问什么。我创造了这把小提琴: 在fiddle中,我尝试复制了一个异步场景。这只是一个示例,但在AJAX调用中,如果我不使用$scope.$apply()列表不会得到更新。我想知道每次我调用AJAX更新列表时使用$scope.$apply()是否安全,或者是否有其他机制可以使用 我为复制场景而编写的代码(与fiddle中的代码相同): HTML <div ng-controller="MyCtrl"> <li ng-repeat="item in items

我想标题很清楚我在问什么。我创造了这把小提琴:

在fiddle中,我尝试复制了一个
异步
场景。这只是一个示例,但在AJAX调用中,如果我不使用
$scope.$apply()
列表不会得到更新。我想知道每次我调用AJAX更新列表时使用
$scope.$apply()
是否安全,或者是否有其他机制可以使用

我为复制场景而编写的代码(与fiddle中的代码相同):

HTML

<div ng-controller="MyCtrl">
  <li ng-repeat="item in items">
    {{item.name}}
  </li>
  <button ng-click="change()">Change</button>
</div>

编辑不清楚OP是否试图模拟后端调用。即便如此,使用
$timeout
服务是避免手动调用
$scope.$apply
的一种很好的方法,并且是比使用Promise更普遍适用的解决方案(在您没有调用
$http
的情况下,通过使用承诺将更改包装到下一个周期并不总是有意义的)。
更新代码以使用,它应该可以工作,而无需调用
$apply

$timeout
是本机
setTimeout
的包装,有一个重要区别:
$timeout
将至少延迟执行到下一个
$digest
周期运行

因此,不延迟传入仍会将执行延迟到下一个周期。传入2000会将执行延迟到2000毫秒后的下一个周期

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


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

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

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

但您可以按照另一个答案中的建议使用$timeout


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

为此,您需要使用创建并返回一个承诺。然后简单地调用它,并通过对返回的承诺调用
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;
    }
}

如果要插入API Rest调用,请在
控制器中使用返回的
承诺
,而不是在Rest调用中设置作用域

$http.get('uri')
  .success(function(data) {
    $scope.items = data
});
避免使用
$apply()

$scope.$apply()
应尽可能靠近异步事件绑定 可能

不要随意将其散布在代码中。如果您正在执行If
(!$scope.$$phase)$scope.$apply()
这是因为你不够高 在调用堆栈中

关于你的问题:

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

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

      这是非常明确的角度文件


      $http或$timeout或ng click,ng-…的回调中包含$apply()。因此,当人们说在执行角度方式时不必使用$apply()时,就是这样。不过,其中一个答案提到角度事件处理程序也包含$apply()。这不是真的,或者用户的意思是只需ng click类型的事件(同样是ng-…)。如果事件在$http或$timeout或ng click之外的rootScope(或任何范围)上广播,例如:从自定义服务,则需要使用$apply()在你的作用域上,$rootScope.$broadcast也是一种有角度的方式。在大多数情况下,我们不需要这样做,因为当发生某些事情时,应用程序的状态会发生变化。例如,单击、选择更改等。这些都是有角度的术语,它们已经在使用$apply()当我们使用ng时,请分别单击ng change。使用Signal r或socket.io处理服务器端事件时,以及在编写自定义指令时,如果只需要更改指令的作用域,那么使用$apply()是非常必要的。更好的方法是使用
      $scope.$applyAsync();
      而不是
      $scope.$apply();

      此处给出了避免使用$scope.$apply()的原因:


      是否尝试模拟REST调用?如果是这样:$http请求返回一个承诺,您可以在控制器内部使用.then()来更改作用域。不要设置新的作用域和$apply()在REST调用内部。因为,您现在所做的是毫无意义的。我知道这没有帮助,但Angular 2.0已设置为修复此问题。每个范围更改都会在摘要中运行。API调用中的超时对我来说很难闻。如果服务器需要更多时间怎么办?如果它被调用2或3次怎么办?@gruberb。那么在什么情况下我们使用apply(),是否存在只有apply()有效的情况?或者换句话说,什么时候我们应该使用apply()?我认为当您觉得需要apply()时,可以做得更好。因为$rootScope,$apply()应该在非常罕见的情况下使用。我想此人希望插入一个REST调用,其中的建议是将承诺与.success一起使用(),那么
      if($scope.$$phase){
          $scope.$apply();
      }
      
      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;
          }
      }
      
      $http.get('uri')
        .success(function(data) {
          $scope.items = data
      });