Custom sort function in ng-repeat
The following link explains filters in Angular extremely well. It shows how it is possible to define custom sort logic within an ng-repeat. http://toddmotto.com/everything-about-custom-filters-in-angular-js
For sorting object with properties, this is the code I have used: (Note that this sort is the standard JavaScript sort method and not specific to angular) Column Name is the name of the property on which sorting is to be performed.
self.myArray.sort(function(itemA, itemB) {
if (self.sortOrder === "ASC") {
return itemA[columnName] > itemB[columnName];
} else {
return itemA[columnName] < itemB[columnName];
}
});
The accepted solution only works on arrays, but not objects or associative arrays. Unfortunately, since Angular depends on the JavaScript implementation of array enumeration, the order of object properties cannot be consistently controlled. Some browsers may iterate through object properties lexicographically, but this cannot be guaranteed.
e.g. Given the following assignment:
$scope.cards = {
"card2": {
values: {
opt1: 9,
opt2: 12
}
},
"card1": {
values: {
opt1: 9,
opt2: 11
}
}
};
and the directive <ul ng-repeat="(key, card) in cards | orderBy:myValueFunction">
, ng-repeat may iterate over "card1" prior to "card2", regardless of sort order.
To workaround this, we can create a custom filter to convert the object to an array, and then apply a custom sort function before returning the collection.
myApp.filter('orderByValue', function () {
// custom value function for sorting
function myValueFunction(card) {
return card.values.opt1 + card.values.opt2;
}
return function (obj) {
var array = [];
Object.keys(obj).forEach(function (key) {
// inject key into each object so we can refer to it from the template
obj[key].name = key;
array.push(obj[key]);
});
// apply a custom sorting function
array.sort(function (a, b) {
return myValueFunction(b) - myValueFunction(a);
});
return array;
};
});
We cannot iterate over (key, value) pairings in conjunction with custom filters (since the keys for arrays are numerical indexes), so the template should be updated to reference the injected key names.
<ul ng-repeat="card in cards | orderByValue">
<li>{{card.name}} {{value(card)}}</li>
</ul>
Here is a working fiddle utilizing a custom filter on an associative array: http://jsfiddle.net/av1mLpqx/1/
Reference: https://github.com/angular/angular.js/issues/1286#issuecomment-22193332
Actually the orderBy
filter can take as a parameter not only a string but also a function. From the orderBy
documentation: https://docs.angularjs.org/api/ng/filter/orderBy):
function: Getter function. The result of this function will be sorted using the <, =, > operator.
So, you could write your own function. For example, if you would like to compare cards based on a sum of opt1 and opt2 (I'm making this up, the point is that you can have any arbitrary function) you would write in your controller:
$scope.myValueFunction = function(card) {
return card.values.opt1 + card.values.opt2;
};
and then, in your template:
ng-repeat="card in cards | orderBy:myValueFunction"
Here is the working jsFiddle
The other thing worth noting is that orderBy
is just one example of AngularJS filters so if you need a very specific ordering behaviour you could write your own filter (although orderBy
should be enough for most uses cases).