sorting values of a hash object in ngRepeat – angularJS

All we need is an easy explanation of the problem, so here it is.

I have an object like this:

$scope.phones = new Object();
$scope.phones['id1'] = {
    "name":"Phone Name1",
    "dateReleased":"2012-1-09 15:48:24"
};
$scope.phones['id2'] = {
    "name": "Phone Name2",
    "dateReleased":"2012-3-12 15:32:11"
};
$scope.phones['id3'] = {
    "name": "Phone Name3",
    "dateReleased":"2012-2-10 13:53:32"
};

I’m displaying this using ngRepeat. I’m not able to order by dateReleased. Also, ordering in reverse isn’t working. My ngRepeat looks this:

<li ng-repeat="phone in phones | orderBy:dateReleased:true">
    <p>{{phone.name}}</p>
    <p>{{phone.dateReleased}}</p>
</li>

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

While ngRepeat can iterate a hash object, like $scope.phones in your example, the built-in orderBy filter will not work. I believe this is due to the way objects are stored. As other’s have noted, you need to convert the hash to an array. While you can do this using the methods suggested above, I prefer to do it using a custom filter. This gives me the benefit of not having to alter my hash directly, and also let’s me reuse the filter with other hashes.

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.

<li ng-repeat="phone in phones | orderObjectBy:'dateReleased':true">
  <p>{{phone.name}}</p>
  <p>{{phone.dateReleased}}</p>
</li>

I’ve got a post on my blog regarding this topic.

Method 2

If you check the documentation it says that the expression in orderBy can be a function, a string or an Array. An therefore you need dateReleased to be a string: ‘dateReleased’

Also you need your phones Object be an actual Array.

Try:

$scope.phones = [{
        "name":"Phone Name1",
        "dateReleased":"2012-1-09 15:48:24"
    },{
        "name": "Phone Name2",
        "dateReleased":"2012-3-12 15:32:11"
    },{
        "name": "Phone Name3",
        "dateReleased":"2012-2-10 13:53:32"
    }];

<li ng-repeat="phone in phones | orderBy:'dateReleased':true">
    <p>{{phone.name}}</p>
    <p>{{phone.dateReleased}}</p>
</li>

Method 3

Both of the other answers get you part way there, but not all the way…

You’ll need to create a function on your scope that converts the object to an array like so:

$scope.phonesArray = function() {
    var result = [];
    angular.forEach($scope.phones, function(phone, id) {
      result.push(phone);
    });
    return result;
};

Then you’d call that instead of your object in your ngRepeat:

<li ng-repeat="phone in phonesArray() | orderBy:'dateReleased':true">
    <p>{{phone.name}}</p>
    <p>{{phone.dateReleased}}</p>
</li>

Also: Notice that 'dateReleased' is a string, so it knows to $eval that string off of the current item, otherwise it will check the parent $scope.dateReleased, which doesn’t exist.

Here is a plunk for what I think you’re trying to do

EDIT: You can also “convert” the object to an array and store it on the $scope, if you’re worried about the function to do so being too “expensive”, but that shouldn’t be an issue, as you’re developing a clientside app for one user, and not a server application for many users, meaning you have a little wiggle room for “expensive”. (Which it won’t be anyway)

Method 4

First of all, you need to understand that ng:filter and ng:orderBy work with Arrays (an ordered collection of items), but you’re trying to use them on Object (_un_ordered collection of items). One possible approach is to collect all the objects into an array, then proceed with ng-repeat on it instead. Like this:

<ul ng-init="phones = [
      {name:'Phone Name 1', dateReleased:'2011-1-09 15:48:24'}
    , {name:'Phone Name 2', dateReleased:'2012-3-12 15:32:11'}
    , {name:'Phone Name 3', dateReleased:'2012-2-10 13:53:32'}]; 
    pred = '-dateReleased';" >
    <li ng-repeat="phone in phones | orderBy:pred">
        <p>{{phone.name}}</p>
        <p>{{phone.dateReleased}}</p>
    </li>
</ul>

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply