Angularjs无限消化循环与ng-repeat和过滤器

时间:2014-03-14 12:56:48

标签: javascript angularjs loops infinite digest

我在angularjs中创建了一个自定义过滤器,它按日期对我的元素进行分组。

这是HTML部分:

<table ng-repeat="(index, groupData) in something = (recentTasks | groupBy:dataGroupBy) track by $index">

过滤器如下所示:

module.filter('groupBy', function () {
    return function(items, field) {
        var groups = [];

        switch (field) {
            case 'week':
                angular.forEach(items, function(item) {
                    var parsed = parseDateTime(item.date);
                    var date = new Date(parsed.year, parsed.month - 1, parsed.day);
                    var back = calculateWeeksBack(date);

                    if (groups[back] == undefined)
                    {
                        groups[back] = {
                            time_back: calculateWeeksBack(date),
                            tasks: []
                        };
                    }

                    groups[back].tasks.push(item);
                    groups[back].total_time += item.time;
                });

                break;

            case 'month':
                angular.forEach(items, function(item) {
                    var parsed = parseDateTime(item.date);
                    var date = new Date(parsed.year, parsed.month - 1, parsed.day);
                    var back = calculateMonthsBack(date);

                    if (groups[back] == undefined)
                    {
                        groups[back] = {
                            time_back: calculateMonthsBack(date),
                            tasks: []
                        };
                    }

                    groups[back].tasks.push(item);
                    groups[back].total_time += item.time;
                });

                break;

            default:
                angular.forEach(items, function(item) {
                    var parsed = parseDateTime(item.date);
                    var date = new Date(parsed.year, parsed.month - 1, parsed.day);
                    var back = calculateDaysBack(date);

                    if (groups[back] == undefined)
                    {
                        groups[back] = {
                            time_back: calculateDaysBack(date),
                            tasks: []
                        };
                    }

                    groups[back].tasks.push(item);
                    groups[back].total_time += item.time;
                });
        }
        return groups;
    }
});

它的作用 - 输入(recentTasks)是一系列任务。每个任务都定义了一个“日期”参数。我需要按日期将这些任务分开 - 每天都在单独的表格中。它有效,但我获得了无限的消化循环。

你能帮我解决一下我的问题吗?或者有更好的解决方案吗?

编辑: 输入和输出的例子:

$scope.items = [
    {name: 'Abc', date: '2014-03-12'},
    {name: 'Def', date: '2014-03-13'},
    {name: 'Ghi', date: '2014-03-11'},
    {name: 'Jkl', date: '2014-03-12'}
]

输出必须按如下方式分组:

[
    '2013-03-11': [
        {name: 'Ghi'}
    ],
    '2013-03-12': [
        {name: 'Abc'},
        {name: 'Jkl'}
    ],
    '2013-03-13': [
        {name: 'Def'}
    ]
]

因为每一天的项目都在HTML结构的分隔表中。

<table ng-repeat="dayData in groupBy(items, 'day')">
    <thead>
        <tr><td>{{ dayData.date }}</td> </tr>
    </thead>
    <tbody>
        <tr ng-repeat="item in dayData.items">
            <td>{{ item.name }}</td>
        </tr>
    </tbody>
</table>

3 个答案:

答案 0 :(得分:9)

要了解为什么会发生这种情况,您需要了解摘要周期。 Angular基于&#34;脏检查&#34;,摘要周期是Angular迭代范围内的所有属性以查看哪些已更改。如果任何属性发生了变化,它会触发所有属性的手表,让他们知道发生了什么事。由于手表可以更改示波器的属性,因此在手表完成后,Angular会进行另一轮脏检查。摘要循环在遍历所有属性时停止,并且它看到它们都没有更改。当手表始终为属性设置新值时,会发生无限摘要。这样的事情可能会更好地解释它:

$scope.myNumber = Math.random();
$scope.$watch('myNumber', function() {
  $scope.myNumber = Math.random();
});

也就是说,我们的手表永远不会停止被调用,因为它总是会改变myNumber的值。导致错误的另一个常见原因是,当您有两个属性和两个监视时,如下所示:

$scope.prop1 = Math.random();
$scope.prop2 = Math.random();

$scope.$watch('prop1', function() {
  $scope.prop2 = Math.random();
});
$scope.$watch('prop2', function() {
  $scope.prop1 = Math.random();
});

这些手表将在无限循环中相互触发。

所以在你的情况下发生的事情是你的过滤器总是返回一个包含新对象的新数组。并且Angular不会通过检查所有属性并比较它们来比较对象,而是添加$$hashkey属性并使用它来与其他对象进行比较。如果两个对象具有相同的$$hashkey属性,则认为它们相等。因此,即使您每次都返回相同的数据结构,Angular也会将其视为一个新对象并运行另一个摘要周期,直到它放弃为止。

因此,要使您的过滤器起作用,您需要更改代码,以便对于传入的相同参数,它返回一个具有相同对象的数组,如同对这些对象的相同引用。它应该足以确保您不必为groups[back]创建新对象,但重用之前的对象。

修改

好吧,我会让你失望并说我建议你重新考虑你的过滤器。对于其他开发人员(或者您自己一年后的一年),您有一个ng-repeat的过滤器并没有返回列表的子集来重复,但是会有点混乱,但是而是一种新的数据结构。举个例子:

// Controller
$scope.items = [
  {name: 'Buy groceries', time: 10},
  {name: 'Clean the kitchen', time: 20}
];

// Template
<li ng-repeat="item in items | groupBy:something">
  {{item.total_time}}
</li>

有人会查看控制器以查看item包含的内容,并且他们会看到它具有nametime属性。然后他们查看模板,看到那里有total_time属性,并且他们不知道它来自groupBy过滤器,因为Angular中的过滤器通常不会{&#39}。 t改变对象。

我认为您应该将分组代码提取到范围内的函数(或创建服务),并仅使用过滤器来过滤掉不相关的项目。

这样,您的模板将会是这样的:

<li ng-repeat="item in groupBy(items, 'something')">
  {{item.total_time}}
</li>

这将更加容易混淆,因为很明显groupBy()方法返回一个新的数据结构。它还会为您修复无限摘要错误。

答案 1 :(得分:3)

Multiple sources会告诉你不要在每次调用ngRepeat函数时创建新的可变对象,因为摘要周期会多次检查以确定对象是否相同然后才能确定它是否稳定模型,每个新调用将创建一个等效但不相同的对象。正如其他人所提到的,&#34;正确的解决方案是使用稳定的模型,这意味着将数组分配给范围/控制器而不是使用吸气剂。&#34;我在使用嵌套的ngRepeats时遇到了这个问题,这使得在没有getter的情况下很难做到这一点。

我找到的最简单的解决方案是使用ngInit初始化变量以引用函数调用的结果,然后使用ngRepeat通过该稳定变量。例如:

<div ng-init="issues=myCtrl.getUniqueIssues()">
    <div ng-repeat="issue in issues">{{issue.name}}</div>
</div>

我的来源也有一个带过滤器的示例:https://tahsinrahit.wordpress.com/2015/01/04/solution-of-infinite-digest-loop-error-for-ng-repeat-with-object-property-filter-in-angularjs/

答案 2 :(得分:1)

有两种方法可以解决这个问题:

  1. 对数组的每个元素使用$$ hashKey属性。 Angular不会通过检查所有属性并比较它们来比较对象,而是添加$$ hashkey属性并使用它来与其他对象进行比较。如果两个对象具有相同的$$ hashkey属性,则认为它们相等。因此,即使您每次都返回相同的数据结构,Angular也会将其视为一个新对象并运行另一个摘要周期,直到它放弃为止。 见:

    • 老字号与$ digest问题 - jsfiddle.net/LE6Ay

    • 添加了$ hashKey属性-jsfiddle.net/LE6Ay/1

  2. 不要在每次getList()调用时创建新对象。正确的解决方案是使用稳定模型,这意味着将数组分配给范围/控制器而不是使用吸气剂。