如何从控制器中推送到由 ng-repeat 迭代的数组?

How to push to an array that is iterated by ng-repeat from within a controller?

这里是 Angular 我不知道的行为。

假设我在控制器中有一个项目数组,我在视图中重复它们。我在视图中有一个按钮,当我点击它时,一个项目被推入数组。 ng-repeated 列表在视图中更新。到目前为止一切都很好。

但是 如果我只是在控制器内的数组中推送一个新项目(比方说,我设置了一个超时时间),视图中的 ng-repeat 将不会注册数组的更改。

plunker 示例:http://plnkr.co/edit/1mlOpGVOXmnCAa8W5KEx?p=preview

HTML:

<!DOCTYPE html>
<html>

  <head>
    <link rel="stylesheet" href="style.css">
    <script src="http://apps.bdimg.com/libs/angular.js/1.4.0-beta.4/angular.min.js"></script>
    <script src="script.js"></script>
  </head>

  <body ng-app="myApp">
    <main ng-controller="MainController as ctrl">
      <button ng-click="ctrl.addName()">Add an item</button>
      <ul>
        <li ng-repeat="name in ctrl.names">
          {{name.name}}
        </li>
      </ul>
    </main>
  </body>

</html>

JS:

angular.module('myApp', [])
  .controller('MainController', function(){
    var self = this;

    self.test = 2;
    self.names = [{name: 'example'}];

    self.addName = function(){
      self.names.push({name: 'new example'});
      console.log(self.names)
    };

    setTimeout(function(){
      self.names.push({name: 'another example'});
      console.log(self.names);
    }, 1000);

  });

观察到的行为:单击按钮将触发 addName 函数并将一个项目添加到列表中。 setTimeout 但是会在数组中推送一个项目,但这不会反映在视图中。在检查控制台日志的结果时,我看到显示的项目有一个 $$hashKey 属性,并且对数组的简单推送将导致缺少此 属性 的项目.

问题:在控制器中向数组添加项目的正确方法是什么?我应该使用 $digest 还是 $apply 之类的?

Angular 通过定期执行脏检查来工作,如果任何观察到的模型值发生变化。它在所谓的 $digest 周期中执行此操作。当 Angular 认为有必要时,这些循环会自动 运行,但如果事件发生在 Angular 上下文之外,Angular 将不知道,不会触发 $digest 循环,将不会检测到更改并且不会更新视图。

当你将一个值推送到数组时(在 Angular 上下文之外),你将需要手动 运行 $digest 循环,例如通过将您的函数包装到 $scope.evalAsync() 中,这将触发范围内的脏检查。

更新: 是的,你也可以调用 $scope.$digest(),但问题是如果 $digest 循环已经 运行ning,你会得到那个不知名的 "$digest() already in progress" 错误。

$scope.evalAsync() 不会遇到这个问题,因为它会异步触发 $digest 循环。更重要的是,如果 $digest 周期已经在进行中,它将尝试在同一个周期中完成工作,并且不会不必要地触发一个新周期。

您还可以看到 Ben Nadel 的 excellent blog post,更详细地解释了所有内容(例如,与使用 $timeout 服务的比较,后者是另一种选择)。