Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Group item detail using AngularJs ng-repeat

I would like to perform a group by function inside of an ng-repeat

Given the following data:

var items = [];
items.push({ id: 1, widgetId: 54, colorId: 45 });
items.push({ id: 2, widgetId: 54, colorId: 72 });
items.push({ id: 3, widgetId: 54, colorId: 29 });
items.push({ id: 4, widgetId: 55, colorId: 67 });
items.push({ id: 5, widgetId: 55, colorId: 29 });
items.push({ id: 6, widgetId: 56, colorId: 29 });
items.push({ id: 7, widgetId: 56, colorId: 72 });
items.push({ id: 8, widgetId: 57, colorId: 75 });

I would like an ng-repeat that results in the following presentation

widgetId 54    colorId: 45 colorId: 72 colorId 29
widgetId 55    colorId: 67 colorId: 29
widgetId 56    colorId: 29 colorId: 72
widgetId 57    colorId: 75

...and markup

<div class="container">
<div class="row">
    <div>widgetId: 54</div>
    <div>
        <div>colorId: 45</div>
        <div>colorId: 72</div>
        <div>colorId: 29</div>
    </div>
</div>
<div class="row">
    <div>widgetId: 55</div>
    <div>
        <div>colorId: 67</div>
        <div>colorId: 29</div>
    </div>
</div>
<div class="row">
    <div>widgetId: 56</div>
    <div>
        <div>colorId: 29</div>
        <div>colorId: 72</div>
    </div>
</div>
<div class="row">
    <div>widgetId: 57</div>
    <div>
        <div>colorId: 75</div>
    </div>
</div>
</div>

Any suggestions that don't include creating separate arrays? The data is coming to me this way and it would be nice to avoid manipulating it.

like image 898
Greg Grater Avatar asked Oct 03 '14 20:10

Greg Grater


2 Answers

Update - the simple, clean way:

Use npm modules! Lodash can handle the groupBy and the memoization needed to avoid an infinite loop as an Angular filter.

npm install lodash
var memoize = require('lodash/function/memoize');
var groupBy = require('lodash/collection/groupBy');
app.filter('groupBy', function() {
  return memoize(groupBy);
});

You may need to use the resolver function of lodash's memoize:

app.filter('groupBy', function() {
  return memoize(function() {
    return groupBy.apply(null, arguments);
  }, function() {
    return JSON.stringify([].slice.call(arguments));
  });
});

But, I really believe you should just simplify all of this and filter in the controller:

$scope.foo = function() { // run this when user clicked button, etc
  $scope.groupedItems = groupBy($scope.items, 'stuff');
};

Old Answer:

I suggest a groupBy filter to modify the data used in the view on the fly. Here's what I came up with. This filter returns a new object each time which will cause an infinite digest cycle, so I wrapped it in my service that fixes those kinds of problems. This one is simply fixed by memoization. Memoization meeans that given the same parameters (input, prop), the exact same output will be returned from a cache, so the same object is returned again, rather than creating a new one that looks the same. This filter also supports nested property names, so you can easily group by a property nested within the objects.

Live Demo

<div class="container">
  <div class="row" ng-repeat="(setKey, set) in items | groupBy:'widgetId'">
    WidgetId: {{setKey}}
    <div ng-repeat="item in set">
      ColorId: {{item.colorId}}
    </div>
  </div>
</div>

The filter:

.filter('groupBy', [
  '$parse', 
  'pmkr.filterStabilize', 
  function($parse, filterStabilize) {

    function groupBy(input, prop) {

      if (!input) { return; }

      var grouped = {};

      input.forEach(function(item) {
        var key = $parse(prop)(item);
        grouped[key] = grouped[key] || [];
        grouped[key].push(item);
      });

      return grouped;

    }

    return filterStabilize(groupBy);

 }])

My filterStabilize service should fix any filter, but any good memoize function will do fine in this case (and most cases).

like image 190
m59 Avatar answered Sep 20 '22 13:09

m59


Try like this:

  <div class="container">
      <div  class="row" ng-repeat="(key, value) in items| groupBy: 'widgetId'">
        <div>widgetId: {{ key }}</div>
        <div>  
          <div ng-repeat="color in value">
            colorId: {{color.colorId}} 
          </div>
        </div>
      </div>
    </div>
like image 22
AvgustinTomsic Avatar answered Sep 19 '22 13:09

AvgustinTomsic