AngularJS Filter for Ordering Objects (Associative Arrays or Hashes) with ngRepeat
I ran into an issue today using the ngRepeat directive in AngularJS. ngRepeat let’s you iterate over a collection (array or object) and repeat a snippet of code for each of the items. Let’s look at a some examples first, before I get into my situation.
Iterating an Array Strings
Let’s assume we have an array of items set in our controller’s scope.
$scope.items = ['red', 'green', 'blue']
We can loop through these items using ngRepeat in the controller’s template.
<ul>
<li ng-repeat="item in items">{{ item }}</li>
</ul>
Iterating an Array of Objects
Ok, now let’s get slightly more advanced. Let’s say we have an array of objects
with a color
field.
$scope.items = [{ color: 'red' }, { color: 'green' }, { color: 'blue' }]
Again, we can use ngRepeat to loop through them. This time we’ll incorporate a
built-in filter to sort by that color
field.
<ul>
<li ng-repeat="item in items | orderBy:'color'">{{ item.color }}</li>
</ul>
Easy enough. The orderBy
filter uses the color
property to sort the objects
before iterating through them.
Iterating an Object of Objects (Acting as an Associative Array or Hash)
Finally, we’ll look at the situation I ran into. Instead of $score.items
being
an array, it’s going to be an object used as an associative array (or hash). So
we have something like this:
$scope.items = {}
$scope.items['color-1'] = { color: 'red' }
$scope.items['color-2'] = { color: 'green' }
$scope.items['color-3'] = { color: 'blue' }
This may look the same as the example above, but it’s not. $score.items
is no
longer an array of objects. It is an object itself (acting like an associative
array or hash) with the properties color-1
, color-2
, and color-3
. We need
to iterate through those properties. ngRepeat does have the ability to do this.
<ul>
<li ng-repeat="(key, item) in items | orderBy:'color'">{{ item.color }}</li>
</ul>
However, the built-in orderBy
filter will no longer work when iterating an
object. It’s ignored due to the way that object fields are stored. This was
the issue I ran into. And here is my solution… a custom filter:
yourApp.filter('orderObjectBy', function () {
return function (items, field, reverse) {
var filtered = []
angular.forEach(items, function (item) {
filtered.push(item)
})
filtered.sort(function (a, b) {
return a[field] > b[field] ? 1 : -1
})
if (reverse) filtered.reverse()
return filtered
}
})
This filter converts the object into a standard array and sorts it by the field
you specify. You can use the orderObjectBy
filter exactly like orderBy
,
including a boolean value after the field name to specify whether the order
should be reversed. In other words, false
is ascending, true
is descending.
<ul>
<li ng-repeat="item in items | orderObjectBy:'color':true">
{{ item.color }}
</li>
</ul>
Note: Because this filter converts the items
object to an array, you will
no longer have access to the key
of the “associative array”, as you do with
the (key, item) in items
snippet. I did not need the key
in my situation, so
this was fine.
Update: Thanks to the work of fmquaglia, this code has been turn into an AngularJS module and is available as ngOrderObjectBy for easy integration into your project.
Update: At the request of one of the commenters below, I’m adding license information. The code above is available under the MIT license.
The MIT License (MIT)
Copyright (c) 2013 Justin Klemm
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.