Angular 分组过滤器

12

angular.js中的ng-repeat中使用条件标记后,我尝试编写了一个自定义过滤器来进行分组。但是我遇到了有关对象标识和正在观察更改的模型的问题,但是我认为我终于解决了它,因为控制台中不再出现错误信息。

结果发现我错了,因为当我尝试将其与其他过滤器(用于分页)结合使用时,就像这样

<div ng-repeat="r in blueprints | orderBy:sortPty | startFrom:currentPage*pageSize | limitTo:pageSize | group:3">
      <div ng-repeat="b in r">
我再次收到了令人害怕的“10 $digest() 迭代已达上限。中止!”错误信息。
这是我的分组过滤器:
filter('group', function() {
  return function(input, size) {
    if (input.grouped === true) {
      return input;
    }
  var result=[];
  var temp = [];
  for (var i = 0 ; i < input.length ; i++) {
      temp.push(input[i]);
      if (i % size === 2) {
          result.push(temp);
          temp = [];
      }
  }
  if (temp.length > 0) {
      result.push(temp);
  }
  angular.copy(result, input);
  input.grouped = true;
  return input;
}; 
}).

注意到输入使用了angular.copy.grouped标记,但是没有效果 :( 我知道 e.g. "10 $digest() iterations reached. Aborting!"由于使用angularjs的过滤器而发生,但显然我没有搞清楚。

此外,我想分组逻辑可能有点简单。无论如何,任何帮助都将不胜感激,因为这让我非常疯狂。


没关系,我刚刚找到了这个额外的线程 https://dev59.com/YmUq5IYBdhLWcg3wF8hQ - Eddie A.
3个回答

14

看起来真正的问题在于你改变了输入内容,而不是创建一个新变量并用它输出过滤结果。这会触发任何正在监视你输入变量的内容。

其实完全没有必要在那里添加“grouped == true”的检查,因为你应该对自己的过滤器有完全的控制权。但如果这对你的应用程序是必需的,那么你需要将“grouped == true”添加到你的过滤器结果中,而不是输入中。

过滤器的工作方式是改变输入并返回不同的东西,然后下一个过滤器处理上一个过滤器的结果...所以你的“filtered”检查大部分是无关紧要的item in items | filter1 | filter2 | filter3其中filter1过滤items,filter2过滤filter1的结果,filter3过滤filter2的结果...如果这样解释有意义的话。

这是我刚刚想出来的一些东西。我不确定(还)是否有效,但它给了你基本的思路。你可以在一边接收一个数组,在另一边输出一个二维数组。

app.filter('group', function(){
   return function(items, groupSize) {
      var groups = [],
         inner;
      for(var i = 0; i < items.length; i++) {
         if(i % groupSize === 0) {
            inner = [];
            groups.push(inner);
         }
         inner.push(items[i]);
      }
      return groups;
   };
});

HTML

<ul ng-repeat="grouping in items | group:3">
    <li ng-repeat="item in grouping">{{item}}</li>
</ul>

编辑

也许在您的代码中看到所有这些过滤器更美观,但它似乎会引起问题,因为它需要在 $digest 上不断重新评估。因此我建议您像这样做:

app.controller('MyCtrl', function($scope, $filter) {
   $scope.blueprints = [ /* your data */ ];
   $scope.currentPage = 0;
   $scope.pageSize = 30;
   $scope.groupSize = 3;
   $scope.sortPty = 'stuff';

   //load our filters
   var orderBy = $filter('orderBy'),
       startFrom = $filter('startFrom'),
       limitTo = $filter('limitTo'),
       group = $filter('group'); //from the filter above

   //a method to apply the filters.
   function updateBlueprintDisplay(blueprints) {
        var result = orderBy(blueprints, $scope.sortPty);
        result = startForm(result, $scope.currentPage * $scope.pageSize);
        result = limitTo(result, $scope.pageSize);
        result = group(result, 3);
        $scope.blueprintDisplay = result;
   }

   //apply them to the initial value.
   updateBlueprintDisplay();

   //watch for changes.
   $scope.$watch('blueprints', updateBlueprintDisplay);
});

然后在你的标记中:

<ul ng-repeat="grouping in blueprintDisplay">
   <li ng-repeat="item in grouping">{{item}}</li>
</ul>

...我确信里面有拼写错误,但那就是基本的想法。


再次编辑:我知道您已经接受了这个答案,但最近我学到了一种更好的方法,您可能会更喜欢:

<div ng-repeat="item in groupedItems = (items | group:3 | filter1 | filter2)">
    <div ng-repeat="subitem in items.subitems">
    {{subitem}}
    </div>
</div>

这将在您的 $scope 上动态创建一个名为 $scope.groupedItems 的新属性,应该可以有效地缓存您过滤和分组后的结果。

试一试,如果行得通,请告诉我。如果不行,我想其他答案可能更好。


1
我看了你说的话,但实际上我尝试了所有这些技巧,因为似乎angular通过引用相等性来检测修改。你提出的基本上是我最初的想法,但证明有问题。只是为了记录,你认为angular应该调用group()函数多少次?我很惊讶地发现它不止一次。后来我理解到,当angular检测到对于相同的输入它获得了“相同”的结果时,它会停止调用它,即当它“稳定”时。问题在于它如何解释“相同”。希望这有意义。 - ebottard
嗯,你能详细说明一下你提出的解决方案吗?我有点喜欢在我的原始问题中解释的链接过滤器的功能(我进行分页、排序等操作)。 - ebottard
嗯,我想到的解决方法是创建一个方法来构建你想要显示的对象结构。这样做也更加高效,因为它不需要在每个$digest上进行处理。你甚至可以使用过滤器,在我的答案中放一些伪代码,或许会有所帮助。 - Ben Lesh
@blesh - 感谢您在视图中缓存过滤数组的提示。官方文档中是否有提到?我可以问一下您学习这个知识的来源吗? - tamakisquare
这个过滤器似乎可以满足我的需求(根据定义的值对项目进行分组)。然而,当涉及到上述$digest问题的所有内容时,我有点迷失。是否有一种解决方案,不需要我在控制器层面做任何事情?此外,有没有办法在模板级别识别第一个分组,使用这个过滤器? - flashpunk
显示剩余8条评论

3

无论如何,我仍然看到$ digest错误,这很令人困惑:plnkr.co/edit/tHm8uYfjn8EJk3cG31DP- blesh Jan 22 at 17:21

这里是plunker forked,使用underscore的记忆函数修复$ digest错误: http://underscorejs.org/#memoize

问题在于Angular在每次迭代期间都尝试将过滤的集合处理为不同的集合。为确保过滤器的返回始终返回相同的对象,请使用memoize。

http://en.wikipedia.org/wiki/Memoization

另一个下划线分组的例子:Angular过滤器有效但会导致“10 $digest迭代次数达到”

1
你可以使用 angular.filter 模块的 groupBy 过滤器,然后像这样做:
用法:(key, value) in collection | groupBy: 'property'或者 'propperty.nested'
JS:

$scope.players = [
  {name: 'Gene', team: 'alpha'},
  {name: 'George', team: 'beta'},
  {name: 'Steve', team: 'gamma'},
  {name: 'Paula', team: 'beta'},
  {name: 'Scruath', team: 'gamma'}
];

HTML:

<ul ng-repeat="(key, value) in players | groupBy: 'team'" >
  Group name: {{ key }}
  <li ng-repeat="player in value">
    player: {{ player.name }} 
  </li>
</ul>
<!-- result:
  Group name: alpha
    * player: Gene
  Group name: beta
    * player: George
    * player: Paula
  Group name: gamma
    * player: Steve
    * player: Scruath

当我将它放入Plnkr中时,这并没有起作用。 - Eddie A.
@EddieA. 请提供一个例子。 - a8m
按组分组仅受支持于Angular 1.4及更高版本,它将在旧版本中失败。以防你想知道为什么它不起作用。 - mbokil
抱歉,但是您犯了一个错误。这里有一个关于group-by过滤器和angular.js-1.3的示例:http://jsbin.com/citewi/edit?html,js,output - a8m

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接