2

I have been trying for days to get a working version of a grouping filter for Angular. The goal is to group and filter a list of items, using dynamic group and search terms. The end product will have two levels of ng-repeats as well as filter terms, but below I've distilled the core issue to just a single ng-repeat and no search filtering.

The issue is described in this question, namely that I get "10 digest" errors in the JS console. I have tried the suggestion in that thread, namely that I use _.memoize(). This works for the initial load, but somehow doesn't update the ng-repeat when I update the model. I'm also unable to determine how "track by" would work for me.

For example code desired output is:

A selected: "One"
B selected: "tres uno"

Here is the version with digest errors (check the JS console), but with output working as desired: http://plnkr.co/9TJvZk

<!DOCTYPE html>
<html>

  <head>
    <script src="//code.angularjs.org/1.3.0/angular.js" data-semver="1.3.0" data-require="angular.js@*"></script>
    <script data-require="underscore.js@*" data-semver="1.6.0" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
    <script>
      var app = angular.module("MyApp", []);

      app.controller('MyCtrl', function($scope) {
        $scope.filter_items = {};
        $scope.filter_items.group_1 = 'propb';
        $scope.items = [{id:"1", propa:"one", propb:"uno"},{id:"2", propa:"one", propb:"tres"}]
        return 
      });

      app.filter('myFilter', function() {
          return function(items, filter_items) {
                  return _.groupBy(items, filter_items.group_1);
          }
      });      
    </script>
  </head>

  <body ng-app="MyApp">
    <div ng-controller="MyCtrl">

      Group by<br>
        <select ng-model="filter_items.group_1">
                <option value="propa">A</option>
                <option value="propb">B</option>
        </select>

      <div ng-repeat="(group1, g_items) in items| myFilter:filter_items">
            <h2>{{group1}}</h2>
      </div>

    </div>
  </body>

</html>

And error free, but not updating when model does: http://plnkr.co/N41D2Y

<!DOCTYPE html>
<html>

  <head>
    <script src="//code.angularjs.org/1.3.0/angular.js" data-semver="1.3.0" data-require="angular.js@*"></script>
    <script data-require="underscore.js@*" data-semver="1.6.0" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
    <script>
      var app = angular.module("MyApp", []);

      app.controller('MyCtrl', function($scope) {
        $scope.filter_items = {};
        $scope.filter_items.group_1 = 'propb';
        $scope.items = [{id:"1", propa:"one", propb:"uno"},{id:"2", propa:"one", propb:"tres"}]
        return 
      });

      app.filter('myFilter', function() {
          return _.memoize(function(items, filter_items) {
                  return _.groupBy(items, filter_items.group_1);
              }
          );
      });      
    </script>
  </head>

  <body ng-app="MyApp">
    <div ng-controller="MyCtrl">

      Group by<br>
        <select ng-model="filter_items.group_1">
                <option value="propa">A</option>
                <option value="propb">B</option>
        </select>

      <div ng-repeat="(group1, g_items) in items| myFilter:filter_items">
            <h2>{{group1}}</h2>
      </div>

    </div>
  </body>

</html>

How do I get working dynamic grouping in AngularJS without the digest errors?

Community
  • 1
  • 1
Josh Diehl
  • 2,913
  • 2
  • 31
  • 43

3 Answers3

2

you mean like this

<!DOCTYPE html>
<html>

  <head>
    <script src="//code.angularjs.org/1.3.0/angular.js" data-semver="1.3.0" data-require="angular.js@*"></script>
    <script data-require="underscore.js@*" data-semver="1.6.0" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
    <script>
      var app = angular.module("MyApp", []);

      app.controller('MyCtrl', function($scope) {
        $scope.filter_items = {};
        $scope.filter_items.group_1 = 'propb';
        $scope.items = [{id:"1", propa:"one", propb:"uno"},{id:"2", propa:"one", propb:"tres"}];

        return;
      });

      app.filter('myFilter', function() {
        var m = {};

        return function(items, filter_items) {

          return (filter_items.group_1 in m) ? m[filter_items.group_1] : (m[filter_items.group_1] = _.groupBy(items, filter_items.group_1) );
        }
      });      
    </script>
  </head>

  <body ng-app="MyApp">
    <div ng-controller="MyCtrl">

      Group by<br>
      <select ng-model="filter_items.group_1">
          <option value="propa">A</option>
          <option value="propb">B</option>
      </select>

      <div ng-repeat="(group1, g_items) in items| myFilter:filter_items">
          <h2>{{group1}}</h2>
      </div>

    </div>
  </body>

</html>

I updated the code above as well as the plunker, just to illustrate your code is workable. But this angular-filter project is awesome, check it out.

wayne
  • 3,410
  • 19
  • 11
  • This doesn't actually group the items, notice in your example "one" is repeated twice whereas if it were group it would show once. Additionally, I need to use a custom filter, as the real code will have two nested ng-repeats, as well as code to actually filter the result set based on a string. – Josh Diehl Oct 20 '14 at 04:38
  • can you update your question say when select A what is the output, when select B what is the output. – wayne Oct 20 '14 at 04:40
0

The reason for the digest error is that each call to _.groupBy returns a new object, so every time angular checks it against the old value it is different, leading to another digest.

As the list is only filtered when filter_items.group_1 changes, it's simple to watch this in the controller and perform the group by there:

app.controller('MyCtrl', function($scope) {
  $scope.filter_items = {};
  $scope.filter_items.group_1 = 'propb';
  $scope.items = [{id:"1", propa:"one", propb:"uno"},{id:"2", propa:"one", propb:"tres"}];

  $scope.$watch('filter_items.group_1', function (group) {
    $scope.groupedItems = _.groupBy($scope.items, group);
  });
});

Then:

<div ng-repeat="(group1, g_items) in groupedItems">
  <h2>{{group1}}</h2>
</div>
noj
  • 6,740
  • 1
  • 25
  • 30
0

I believe you can achieve the same result without using a filter. I have successfully achieved this by using a watcher in the controller and grouping in the watcher function. This doesn't have the 10 digest errors and would work when your selection changes.

  <head>
    <script src="//code.angularjs.org/1.3.0/angular.js" data-semver="1.3.0" data-require="angular.js@*"></script>
    <script data-require="underscore.js@*" data-semver="1.6.0" src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.6.0/underscore-min.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
    <script>
      var app = angular.module("MyApp", []);

      app.controller('MyCtrl', function($scope) {
        $scope.filter_items = {};
        $scope.filter_items.group_1 = 'propb';
        $scope.items = [{id:"1", propa:"one", propb:"uno"},{id:"2", propa:"one", propb:"tres"}]
        $scope.$watch('filter_items.group_1', function(newValue){
          $scope.grouped = _.groupBy($scope.items, newValue)
        })
        return 
      });  
    </script>
  </head>

  <body ng-app="MyApp">
    <div ng-controller="MyCtrl">

      Group by<br>
        <select ng-model="filter_items.group_1">
                <option value="propa">A</option>
                <option value="propb">B</option>
        </select>

      <div ng-repeat="(group1, g_items) in grouped">
            <h2>{{group1}}</h2>
      </div>

    </div>
  </body>

</html>
Caleb Kiage
  • 1,422
  • 12
  • 17