Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular grouping filter

Tags:

angularjs

Following angular.js conditional markup in ng-repeat, I tried to author a custom filter that does grouping. I hit problems regarding object identity and the model being watched for changes, but thought I finally nailed it, as no errors popped in the console anymore.

Turns out I was wrong, because now when I try to combine it with other filters (for pagination) like so

<div ng-repeat="r in blueprints | orderBy:sortPty | startFrom:currentPage*pageSize | limitTo:pageSize | group:3">
      <div ng-repeat="b in r">

I get the dreaded "10 $digest() iterations reached. Aborting!" error message again.

Here is my group filter:

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;
}; 
}).

Note both the use of angular.copy and the .grouped marker on input, but to no avail :( I am aware of e.g. "10 $digest() iterations reached. Aborting!" due to filter using angularjs but obviously I did not get it.

Moreover, I guess the grouping logic is a bit naive, but that's another story. Any help would be greatly appreciated, as this is driving me crazy.

like image 605
ebottard Avatar asked Jan 22 '13 15:01

ebottard


People also ask

What is grouping in angular?

A Group By behavior in an Angular Material table or UI grid creates grouped data rows based on the column values. The Group By in igxGrid allows for visualizing the groups in a hierarchical structure. The grouped data rows can be expanded or collapsed and the order of grouping may be changed through the UI or API.


3 Answers

It looks like the real problem here is you're altering your input, rather than creating a new variable and outputing that from your filter. This will trigger watches on anything that is watching the variable you've input.

There's really no reason to add a "grouped == true" check in there, because you should have total control over your own filters. But if that's a must for your application, then you'd want to add "grouped == true" to the result of your filter, not the input.

The way filters work is they alter the input and return something different, then the next filter deals with the previous filters result... so your "filtered" check would be mostly irrelavant item in items | filter1 | filter2 | filter3 where filter1 filters items, filter2 filters the result of filter1, and filter3 filters the result of filter 2... if that makes sense.

Here is something I just whipped up. I'm not sure (yet) if it works, but it gives you the basic idea. You'd take an array on one side, and you spit out an array of arrays on the other.

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>

EDIT

Perhaps it's nicer to see all of those filters in your code, but it looks like it's causing issues because it constantly needs to be re-evaluated on $digest. So I propose you do something like this:

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);
});

then in your markup:

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

... I'm sure there are typos in there, but that's the basic idea.


EDIT AGAIN: I know you've already accepted this answer, but there is one more way to do this I learned recently that you might like better:

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

This will create a new property on your $scope called $scope.groupedItems on the fly, which should effectively cache your filtered and grouped results.

Give it a whirl and let me know if it works out for you. If not, I guess the other answer might be better.

like image 154
Ben Lesh Avatar answered Nov 11 '22 19:11

Ben Lesh


Regardless, I'm still seeing the $digest error, which is puzzling: plnkr.co/edit/tHm8uYfjn8EJk3cG31DP – blesh Jan 22 at 17:21

Here is the plunker forked with the fix to the $digest error, using underscore's memoize function: http://underscorejs.org/#memoize.

The issue was that Angular tries to process the filtered collection as a different collection during each iteration. To make sure the return of the filter always returns the same objects, use memoize.

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

Another example of grouping with underscore: Angular filter works but causes "10 $digest iterations reached"

like image 42
user2779653 Avatar answered Nov 11 '22 19:11

user2779653


You can use groupBy filter of angular.filter module, and do something like this:
usage: (key, value) in collection | groupBy: 'property'or '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
like image 1
a8m Avatar answered Nov 11 '22 20:11

a8m