When working with AngularJS, we become familiar with the use of the ng-repeat directive to iterate thru an object collection or an array of objects. This kind of implementation works well when displaying the data without sorting it.
The Problem
When there is a need to filter to data and sort the information by a particular property, we reach for the orderBy filter. It is at this point that we realize that depending on the data structure we use, the results may not be the same. To visualize the differences, let’s take a look at an example in which we try to sort vehicles by their year. We want the most recent year at the top.
var list = {};list[2009] = {make:'Nissan',model:'Altima', year:2009};list[2010] = {make:'Nissan',model:'XTerra', year:2010};list[2012] = {make:'Nissan',model:'Maxima', year:2012}; $scope.list= list;
<ul> <li ng-repeat="item in list | orderBy:'-year'" ng-init="item.name = item.year + ' '+ item.make +' '+ item.model">{{item.name}}</li> </ul>
As shown on the previous example, the data from the object collection is not sorted properly. The problem is that the orderBy filter expects an array data type which it can access via a numeric index. An object collection or associative array can only be accessed with a key which can be numeric or a string. We should note that in the most recent releases of AngularJS, the framework now raises an error indicating this problem.
The Solution
To address the problem, we need to provide the filter with an array like structure. This can be done by pushing the elements of our object collection into an array and using the result on the ng-repeat directive. Let’s take a look of how this can be done by modifying our previous example:
var arr = Object.keys(list).map(function(key) { return list[key]; }); $scope.arr = arr; <ul> <li ng-repeat="item in arr | orderBy:'-year'" ng-init="item.name = item.year + ' '+ item.make +' '+ item.model">{{item.name}}</li> </ul>
We can see how the list is sorted properly with the right order. We first created an array from our object collection (list) by getting all the keys and returning the data associated to the key. The end result is an array type. The orderBy filter uses the year property on the object and sorts the data in descending order by using the (-year) expression. This time the filter is able to access each object on the array and sort the data properly.
The one item to take away from this is that we should remember that arrays and associative arrays (hash tables) are different data structures, and the results on our view may be different than expected.
var list = {}; list[2009] = {make:'Nissan',model:'Altima', year:2009}; list[2010] = {make:'Nissan',model:'XTerra', year:2010}; list[2012] = {make:'Nissan',model:'Maxima', year:2012}; $scope.list= list; <ul> <li ng-repeat="item in list | orderBy:'-year'" ng-init="item.name = item.year + ' '+ item.make +' '+ item.model">{{item.name}}</li> </ul> |
var arr = Object.keys(list).map(function(key) { return list[key]; }); $scope.arr = arr; <ul> <li ng-repeat="item in arr | orderBy:'-year'" ng-init="item.name = item.year + ' '+ item.make +' '+ item.model">{{item.name}}</li> </ul> |