【问题标题】:Function in ng-repeat with track by causes Infinite $digest-loopng-repeat 中的函数与 track by 原因 Infinite $digest-loop
【发布时间】:2017-09-26 08:50:06
【问题描述】:

显然我还不了解ng-repeat、$$hashKeys 和track by 背后的机制。

我目前在我的项目中使用 AngularJS 1.6。

问题:

我得到了一个复杂对象的数组,我想用它们在我的视图中呈现一个列表。但要获得所需的结果,我需要先修改(或映射/增强/更改)这些对象:

const sourceArray = [{id: 1, name: 'Dave'}, {id:2, name: Steve}]

const persons = sourceArray.map((e) => ({enhancedName: e.name + e.id})) 

//Thus the content of persons is:
//[{enhancedName: 'Dave_1'}, {enhancedName: 'Steve_2'}]

将它绑定到视图应该像这样工作:

<div ng-repeat="person in ctrl.getPersons()">
    {{person.enhancedName}}
</div>

但这显然会遇到$digest()-循环,因为.map 每次调用时都会返回新的对象实例。由于我通过一个函数将它绑定到 ng-repeat,它会在每个 $digest 中重新评估,模型不稳定并且 Angular 不断重新运行 $digest-cycles,因为这些对象被标记为 $dirty。

我为什么感到困惑

现在这不是一个新问题,有几种解决方案:

在an Angular-Issue from 2012Igor Minar 自己建议手动设置 $$hashKey-Property 来告诉 angular 生成的对象是相同的。 This 是他的工作小提琴,但由于即使是这个非常微不足道的例子在我的项目中使用它时仍然遇到$digest-loop,我尝试在小提琴中升级 Angular-Version。出于某种原因it crashes。

好的...从 Angular 1.3 开始,我们有了track by,它应该基本上解决了这个确切的问题。然而两者都

<div ng-repeat="person in ctrl.getPersons() track by $index">   

和

<div ng-repeat="person in ctrl.getPersons() track by person.enhancedName">   

$digest-loop 导致崩溃。我的印象是track by 语句应该让 Angular 相信它适用于相同的对象,但显然情况并非如此,因为它只是不断检查它们是否有变化。老实说,我不知道如何正确调试导致此问题的原因。

问题:

是否可以使用过滤/修改的数组作为 ng-repeat 的数据源?

我不想将修改后的数组存储在我的控制器上,因为我需要不断更新其数据,然后必须在控制器中手动维护和刷新它,而不是依赖数据绑定。

【问题讨论】:

  • 一个(现在已删除)答案询问有关实际用例的更多详细信息:我的应用程序中的 sourceArray 比此示例中的复杂得多。因此,我想从中删除视图不需要的所有数据(为了便于阅读)。除此之外,我需要将一些信息和业务逻辑映射到其他数据类型(即“当这个数组有 10 个或更多条目时,这个切换按钮应该呈现为‘点击’”)要正确地将它与 ng-model 绑定,我会喜欢将 Object.defineProperty 与包含相应业务逻辑的自定义 getter 和 setter 方法一起使用。

标签: javascript angularjs angularjs-ng-repeat


【解决方案1】:

您提供的“it crashes”小提琴并没有为我产生无限的摘要。事实上:它甚至没有成功引导 Angular 应用程序(看起来在最新的 Angular 中无法以这种方式进行引导)。

我 rewrote it 使用我理解的 Angular 引导机制。就像你说的那样,它重现了崩溃。

我找到了成功的方法track by stringified JSON。

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>

<script>
angular.module('myApp',[])
.controller('Ctrl', ['$scope', function($scope) {
    angular.extend($scope, {
    stringify: function(x) { return JSON.stringify(x) },
    getList: function() {
      return [
        {name:'John', age:25},
        {name:'Mary', age:28}
      ];
    }
  });
}]);
</script>

<div ng-app="myApp">

<div ng-controller="Ctrl">
  I have {{getList().length}} friends. They are:
  <ul>
    <li ng-repeat="friend in getList() track by stringify(friend)">
      [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old.
    </li>
  </ul>
</div>

</div>

即我们提供跟踪功能,stringify()。也可能有一个 Angular 内置函数。

track by $index 也能正常工作——与你的发现相反。我认为 JsFiddle 稍微破坏了实验*

*以下是轶事。我相信我在 JsFiddle 本身方面遇到了一些问题。例如:我的track by stringify() 示例在我分叉 Fiddle 并在新的浏览上下文中再次尝试相同的代码之前不起作用。我相信,一旦我得到any 无限摘要:JsFiddle 将总是 无限摘要。似乎有一些从以前的运行中挥之不去的状态。所以,我建议你在 JsFiddle 中看到的任何失败,你再试一次在一个新的 JsFiddle。

至于为什么你的 $$hashKey 技巧会导致无限摘要——我认为 Angular 不希望 $$hashKey 成为一个函数。因此,它可能不是调用您的函数,而是对分配给$$hashKey 的函数进行引用比较。

由于您每次调用getList() 时都会为$$hashKey 分配一个比较器的新实例:引用可能从不在后续摘要中相等,因此它会一直尝试摘要。

编辑:更新了 StackOverflow 嵌入和 JsFiddle 以使用 HTTPS CDN(以避免违反混合内容安全性)。

【讨论】:

  • 已经非常感谢了 :) 对我来说,你的工作示例还不能工作(在 chrome 和 firefox 中测试)。它仍然不会在视图中打印任何值。我目前正在测试这是否是由您提到的小提琴问题引起的。至于您的其他想法:$index 的曲目对您有用,既是一种解脱(因为我认为它应该有效......)也是坏消息,因为它对我不起作用,我不知道是什么原因造成的.在我在我的问题中链接的示例中,Igor Minar(AngularJS 技术负责人)使用了$$hashKey 技巧,并且由于某种原因它对他有用......
  • 即使没有导航到 JsFiddle 镜像:我发现在 Firefox Nightly 中,如果我单击此 StackOverflow 帖子上的“运行代码 sn-p”,它肯定会将值打印到视图(并且没有摘要控制台中的错误)。所以这进一步证实了 JsFiddle 可能有错。至于 Igor Minar 的 $$hashKey 技巧:Angular 完全有可能在 5 年前以不同的方式工作。您可能会发现同时拥有$$hashKey 和 track by $index 没有意义。假设track by … 为填充$$hashKey 提供了一个策略,那么提供这两种方法他们将互相争斗。
  • @HW 此代码在基于 Chrome/Chromium 的浏览器中由于不相关的原因而失败:Mixed Content security 正在阻止安全上下文 (HTTPS) 上的应用程序通过 HTTP 加载 Angular 库。 StackOverflow 自动将 HTTP 重定向到 HTTPS,因此您无法交叉检查 StackOverflow,但至少您可以尝试访问 JSFiddle over HTTP。或者,检查此updated JSFiddle over HTTP,其中 Angular 是从安全上下文中导入的。代码本身有效!
  • JSON.Stringify 很昂贵,而且不是使用 trackBy 的正确方法。它可能发生的原因是 ng-repeat 表达式是一个函数。这意味着它会在每个摘要周期对其进行评估并创建一个新数组。请参阅 Francesco 的回答,我认为他在正确的轨道上。
  • 任何确定性区分每个数组元素的散列函数就足够了;我使用了 stringify ,因为这就是问题的 Fiddle 中使用的内容。在每个摘要循环中创建一个新数组很好,因为我们跟踪的是数组中的元素的散列,而不是数组本身。
【解决方案2】:

只要被监视的表达式getPersons()返回一个new数组,即使元素相同,使用===比较的$digest循环也不能停止;不管track by 表达式,在ngRepeat 的change detection 之后对渲染 节点起作用。

(function() {
  angular
    .module('app', [])
    .controller('AppController', AppController)

  function AppController($interval) {
    // you may have more performant options here
    const hashFn = angular.toJson.bind(angular)
    // your mapping logic for presentation
    const mapFn = (e) => ({
      enhancedName: e.name + e.id
    })

    // initialization of data
    let sourceArray = [{
      id: 1,
      name: 'Dave'
    }, {
      id: 2,
      name: 'Steve'
    }]

    // initialization of "cache"
    let personList = sourceArray.map(mapFn),
        lastListHash = hashFn(sourceArray)

    Object.defineProperty(this, 'personList', {
      get: function() {
        const hash = hashFn(sourceArray)
        if (hash !== lastListHash) {
          personList = sourceArray.map(mapFn)
          lastListHash = hash
        }

        // you need to return **the same** array
        // if the source has not been updated
        // to make `$digest` cycle happy
        return personList
      }
    })

    // test of changes
    $interval(() => sourceArray.push({
      id: Date.now(),
      name: 'a'
    }), 1000)
  }
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<div ng-app="app">

  <div ng-controller="AppController as ctrl">
    There are {{ctrl.personList.length}} persons.
    <ul>
      <li ng-repeat="person in ctrl.personList track by $index">
        [{{$index + 1}}] {{ person.enhancedName }}
      </li>
    </ul>
  </div>

</div>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-13
    • 2015-06-18
    • 2018-03-18
    • 1970-01-01
    • 1970-01-01
    • 2014-12-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多