【问题标题】:How to push to an array that is iterated by ng-repeat from within a controller?如何从控制器内推送到由 ng-repeat 迭代的数组?
【发布时间】:2015-10-01 14:42:27
【问题描述】:

这是我不知道的 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 之类的?

【问题讨论】:

  • setTimeout 是作为从控制器内调用的函数的示例给出的。我的实际代码响应控制器中触发的事件并将项目推送到数组。
  • 该函数将如何触发?如果你手动调用,你仍然需要包裹在 $timeout 或 $applyAsync 或 $evalAsync 或 $$postDigest 之类的东西中。如果你从 $http.get 调用它,它会自动触发。
  • 我正在使用第三方地理映射库。它返回一个承诺,当这个承诺得到解决时,我正在控制器中调用一个函数。然后,该函数会将返回的任何承诺添加到控制器上的this.names 数组中。但是显然,角度上下文丢失了。所以我想知道如何恢复它。
  • 是的,这不是 Angular 的承诺,因此,您需要使用我提到的上述方法之一手动触发它。 (例如,$scope.$applyAsync(function(){self.names.push({name: 'another example'});});

标签: angularjs controller ng-repeat


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 正如我在评论中提到的,我没有在我的实际代码中使用setTimeout——我正在监听某个对象触发的事件,然后运行一个将元素推送到数组的函数。我在问题中使用的setTimeout 仅用于说明目的。所以我正在寻找一个通用的解决方案,而不是只处理 setTimeout 的特定解决方案。
  • 好的,谢谢,我会相应地更新我的答案。答案基本相同,当事件发生在 Angular 之外时,您需要手动触发 $digest 循环。
  • 我如何手动触发 $digest 循环?是不是类似于self.names.$digest()
  • 差不多。 :) 它是$scope.$digest()(或$scope.$apply()),但这有一些缺点(我会进一步更新我的答案)
  • 啊,我想使用controller as 语法并尽量避开$scope。但这行得通,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-15
  • 1970-01-01
  • 2019-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多