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.
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');
};
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.
<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).
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With