95

What I am trying to do is sort some data by property. Here is example that I tought should work but it doesn't.

HTML part:

<div ng-app='myApp'>
    <div ng-controller="controller">
    <ul>
        <li ng-repeat="(key, value) in testData | orderBy:'value.order'">
            {{value.order}}. {{key}} -> {{value.name}}
        </li>
    </ul>
    </div>
</div>

JS part:

var myApp = angular.module('myApp', []);

myApp.controller('controller', ['$scope', function ($scope) {

    $scope.testData = {
        C: {name:"CData", order: 1},
        B: {name:"BData", order: 2},
        A: {name:"AData", order: 3},
    }

}]);

And the result:

  1. A -> AData
  2. B -> BData
  3. C -> CData

... that IMHO should look like this:

  1. C -> CData
  2. B -> BData
  3. A -> AData

Did I miss something (here is ready JSFiddle to experiment on)?

PrimosK
  • 13,848
  • 10
  • 60
  • 78

10 Answers10

151

AngularJS' orderBy filter does just support arrays - no objects. So you have to write an own small filter, which does the sorting for you.

Or change the format of data you handle with (if you have influence on that). An array containing objects is sortable by native orderBy filter.

Here is my orderObjectBy filter for AngularJS:

app.filter('orderObjectBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    var array = [];
    for(var objectKey in input) {
        array.push(input[objectKey]);
    }

    array.sort(function(a, b){
        a = parseInt(a[attribute]);
        b = parseInt(b[attribute]);
        return a - b;
    });
    return array;
 }
});

Usage in your view:

<div class="item" ng-repeat="item in items | orderObjectBy:'position'">
    //...
</div>

The object needs in this example a position attribute, but you have the flexibility to use any attribute in objects (containing an integer), just by definition in view.

Example JSON:

{
    "123": {"name": "Test B", "position": "2"},
    "456": {"name": "Test A", "position": "1"}
}

Here is a fiddle which shows you the usage: http://jsfiddle.net/4tkj8/1/

Armin
  • 15,582
  • 10
  • 47
  • 64
  • 1
    This is great. I added the option to sort asc/desc: app.filter('orderObjectBy', function(){ return function(input, attribute, direction) { if (!angular.isObject(input)) return input; var array = []; for(var objectKey in input) { array.push(input[objectKey]); } array.sort(function(a, b){ a = parseInt(a[attribute]); b = parseInt(b[attribute]); return direction == 'asc' ? a - b : b - a; }); return array; } }); In HTML: – Jazzy Mar 13 '14 at 18:31
  • @Armin what if this is object like array? i.e. `{1:'Example 1', 2:'Example 2', 3:'Example 3', ...}` – Eugene Apr 09 '14 at 22:54
  • You could just keep the "attribute" parameter empty and remove first two lines in sort function. But this example directive just sorts numbers, not text. `{1:'2', 2:'1'}`would work than. – Armin Apr 17 '14 at 15:59
  • 8
    Great answer BUT we lost the key of the object this way. To keep it, just add it creating new array's rows in the filter e.g. `for(var objectKey in input) { input[objectKey]['_key'] = objectKey; array.push(input[objectKey]); }` Like that we can use `
    `
    – Nicolas Janel Jul 17 '14 at 14:34
  • 7
    It's worth noting that Angular.js **does** support sorting by a property in an array of objects now: `... | orderBy: 'name'`. – Wildhoney Oct 02 '14 at 08:53
  • 2
    @Wildhoney This question is about ordering objects with keys, not arrays containing objects. – Armin Mar 31 '15 at 16:14
  • Hello all can you plz tell me how i can do the same with angular 2 or 4 – George C. May 14 '17 at 09:13
30

It's pretty easy, just do it like this

$scope.props = [{order:"1"},{order:"5"},{order:"2"}]

ng-repeat="prop in props | orderBy:'order'"
Leon
  • 1,136
  • 1
  • 11
  • 17
  • 33
    This does not work for associative arrays, which is what the question is about. – MFB Apr 07 '14 at 23:44
7

Don't forget that parseInt() only works for Integer values. To sort string values you need to swap this:

array.sort(function(a, b){
  a = parseInt(a[attribute]);
  b = parseInt(b[attribute]);
  return a - b;
});

with this:

array.sort(function(a, b){
  var alc = a[attribute].toLowerCase(),
      blc = b[attribute].toLowerCase();
  return alc > blc ? 1 : alc < blc ? -1 : 0;
});
Eric Steinborn
  • 800
  • 1
  • 7
  • 14
6

As you can see in the code of angular-JS ( https://github.com/angular/angular.js/blob/master/src/ng/filter/orderBy.js ) ng-repeat does not work with objects. Here is a hack with sortFunction.

http://jsfiddle.net/sunnycpp/qaK56/33/

<div ng-app='myApp'>
    <div ng-controller="controller">
    <ul>
        <li ng-repeat="test in testData | orderBy:sortMe()">
            Order = {{test.value.order}} -> Key={{test.key}} Name=:{{test.value.name}}
        </li>
    </ul>
    </div>
</div>

myApp.controller('controller', ['$scope', function ($scope) {

    var testData = {
        a:{name:"CData", order: 2},
        b:{name:"AData", order: 3},
        c:{name:"BData", order: 1}
    };
    $scope.testData = _.map(testData, function(vValue, vKey) {
        return { key:vKey, value:vValue };
    }) ;
    $scope.sortMe = function() {
        return function(object) {
            return object.value.order;
        }
    }
}]);
SunnyShah
  • 28,934
  • 30
  • 90
  • 137
4

according to http://docs.angularjs.org/api/ng.filter:orderBy , orderBy sorts an array. In your case you're passing an object, so You'll have to implement Your own sorting function.

or pass an array -

$scope.testData = {
    C: {name:"CData", order: 1},
    B: {name:"BData", order: 2},
    A: {name:"AData", order: 3},
}

take a look at http://jsfiddle.net/qaK56/

Gal Ben-Haim
  • 17,433
  • 22
  • 78
  • 131
3

You should really improve your JSON structure to fix your problem:

$scope.testData = [
   {name:"CData", order: 1},
   {name:"BData", order: 2},
   {name:"AData", order: 3},
]

Then you could do

<li ng-repeat="test in testData | orderBy:order">...</li>

The problem, I think, is that the (key, value) variables are not available to the orderBy filter, and you should not be storing data in your keys anyways

Joshua Wooward
  • 1,508
  • 2
  • 10
  • 12
2

Here is what i did and it works.
I just used a stringified object.

$scope.thread = [ 
  {
    mostRecent:{text:'hello world',timeStamp:12345678 } 
    allMessages:[]
  }
  {MoreThreads...}
  {etc....}
]

<div ng-repeat="message in thread | orderBy : '-mostRecent.timeStamp'" >

if i wanted to sort by text i would do

orderBy : 'mostRecent.text'
James Harrington
  • 3,138
  • 30
  • 32
2

I will add my upgraded version of filter which able to supports next syntax:

ng-repeat="(id, item) in $ctrl.modelData | orderObjectBy:'itemProperty.someOrder':'asc'

app.filter('orderObjectBy', function(){

         function byString(o, s) {
            s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
            s = s.replace(/^\./, '');           // strip a leading dot
            var a = s.split('.');
            for (var i = 0, n = a.length; i < n; ++i) {
                var k = a[i];
                if (k in o) {
                    o = o[k];
                } else {
                    return;
                }
            }
            return o;
        }

        return function(input, attribute, direction) {
            if (!angular.isObject(input)) return input;

            var array = [];
            for(var objectKey in input) {
                if (input.hasOwnProperty(objectKey)) {
                    array.push(input[objectKey]);
                }
            }

            array.sort(function(a, b){
                a = parseInt(byString(a, attribute));
                b = parseInt(byString(b, attribute));
                return direction == 'asc' ? a - b : b - a;
            });
            return array;
        }
    })

Thanks to Armin and Jason for their answers in this thread, and Alnitak in this thread.

Community
  • 1
  • 1
TachikomaGT
  • 940
  • 10
  • 10
1

Armin's answer + a strict check for object types and non-angular keys such as $resolve

app.filter('orderObjectBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    var array = [];
    for(var objectKey in input) {
      if (typeof(input[objectKey])  === "object" && objectKey.charAt(0) !== "$")
        array.push(input[objectKey]);
    }

    array.sort(function(a, b){
        a = parseInt(a[attribute]);
        b = parseInt(b[attribute]);
        return a - b;
    });

    return array;
 }
})
dipole_moment
  • 5,266
  • 4
  • 39
  • 55
1

The following allows for the ordering of objects by key OR by a key within an object.

In template you can do something like:

    <li ng-repeat="(k,i) in objectList | orderObjectsBy: 'someKey'">

Or even:

    <li ng-repeat="(k,i) in objectList | orderObjectsBy: 'someObj.someKey'">

The filter:

app.filter('orderObjectsBy', function(){
 return function(input, attribute) {
    if (!angular.isObject(input)) return input;

    // Filter out angular objects.
    var array = [];
    for(var objectKey in input) {
      if (typeof(input[objectKey])  === "object" && objectKey.charAt(0) !== "$")
        array.push(input[objectKey]);
    }

    var attributeChain = attribute.split(".");

    array.sort(function(a, b){

      for (var i=0; i < attributeChain.length; i++) {
        a = (typeof(a) === "object") && a.hasOwnProperty( attributeChain[i]) ? a[attributeChain[i]] : 0;
        b = (typeof(b) === "object") && b.hasOwnProperty( attributeChain[i]) ? b[attributeChain[i]] : 0;
      }

      return parseInt(a) - parseInt(b);
    });

    return array;
 }
})
dipole_moment
  • 5,266
  • 4
  • 39
  • 55