If have a list of items with date field which is ordered by date. I want to group these items by date somethings like;
and so forth...
What I have is this;
<ul>
<li ng-repeat="listing in listings | filter:query">
<h1 ng-show="$index === 0 || listings[$index - 1].created !== listing.created">
{{listing.created | date:mediumDate}}
</h1>
{{listing.title}}
</li>
</ul>
But this way h1 element becomes the child of li element which messes up presentation. Is there some other way to write this ng-repeat the way I want?
I have a solution that might not work depending on the number of items in your list.
My idea was to apply a filter to transform a simple list into a hierarchical one.
Here's a working jsFiddle. (See the new one at the bottom, really!)
The idea is simple, take an existing list, and rearrange it on-the-fly:
<div ng-repeat="g in items | groupBy:'group' | orderBy:'group'">
<h2>{{g.group}}</h2>
<ul>
<li ng-repeat="item in g.items | orderBy:'title'">{{item.id}}. {{item.title}}</li>
</ul>
</div>
In this case, we've take the original array, and applied a groupBy
filter on it. This filter is actually sort of complex, because it needs to make sure it doesn't modify the array more than necessary, so it has to store a deep copy of the original array to compare against. If you remove that deep copy and inspection, you will end up with $digest
iteration errors every time.
Edit: See bottom for a modified version that performs shallower copies of the array
Here's the filter code:
app.filter("groupBy", function() {
var mArr = null,
mGroupBy = null,
mRetArr = null;
return function(arr, groupBy) {
if(!angular.equals(mArr, arr) || mGroupBy !== groupBy) {
mArr = angular.copy(arr);
mGroupBy = groupBy;
mRetArr = [];
var groups = {};
angular.forEach(arr, function(item) {
var groupValue = item[groupBy]
if(groups[groupValue]) {
groups[groupValue].items.push(item);
} else {
groups[groupValue] = {
items: [item]
};
groups[groupValue][groupBy] = groupValue;
mRetArr.push(groups[groupValue]);
}
});
}
return mRetArr;
};
});
(mArr
, mGroupBy
, and mRetArr
are the "memoized" values, here.)
What's nice about this filter is that it doesn't require that the data come in already grouped. The jsFiddle linked above include a button to dynamically add items to the array at the end, but they still end up sorted into groups.
Again, the real caveat is that it could be slow and/or expensive based on the number of items in your list. Hundreds of moderately-complex items should be OK, but if you have thousands, you should pre-sort them somewhere else before they get to the scope.
At the cost of more code complexity, I modified the filter to copy only the key information instead of deeply copying the whole array. This time, I make a hashmap of the groups containing the items. The items are still referenced, but this time it's an explicit reference, so modifying an item in the tree will not force a resort, but changing an item's group or adding or removing an item will.
New fiddle: http://jsfiddle.net/hhWaX/2/
And here's the new filter:
app.filter("groupBy", function() {
var mArr = null,
mGroupBy = null,
mRetArr = null,
getMemoArr = function(arr, groupBy) {
var ret = {};
angular.forEach(arr, function(item){
var groupValue = item[groupBy];
if(ret[groupValue]) {
ret[groupValue].push(item);
} else {
ret[groupValue] = [item];
}
});
return ret;
};
return function(arr, groupBy) {
var newMemoArr = getMemoArr(arr, groupBy);
if(mGroupBy !== groupBy || !angular.equals(mArr, newMemoArr)) {
mArr = newMemoArr;
mGroupBy = groupBy;
mRetArr = [];
var groups = {};
angular.forEach(arr, function(item) {
var groupValue = item[groupBy]
if(groups[groupValue]) {
groups[groupValue].items.push(item);
} else {
groups[groupValue] = {
items: [item]
};
groups[groupValue][groupBy] = groupValue;
mRetArr.push(groups[groupValue]);
}
});
}
return mRetArr;
};
});
It's a lot more complicated, but this one should have significantly better performance over large sets, because there is the absolute minimum of duplication of the original array.
<li class="divider" ng-if="FormsList[$index].FORM_GROUP!=FormsList[$index-1].FORM_GROUP">
Works perfectly for me. like this
<input type="text" class="form-control" placeholder="Search Form..." ng-model="selectedForm">
<ul class="nav nav-pills nav-stacked" role="menu" ng-repeat="Form in FormsList | filter:selectedForm">
<li class="divider" ng-if="FormsList[$index].FORM_GROUP!=FormsList[$index-1].FORM_GROUP">{{Form.FORM_GROUP}}</li>
<li><a href="#" ng-click="LoadPDF(Form.LOCATION)">{{Form.LINK}}</a></li>
</ul>
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