Orderby not working with dict syntax on ng-repeat

Orderby not working with dict syntax on ng-repeat

I am trying to use ng-repeat with a dictionary style syntax and apply an order to the key value.
(key, value) in something | orderBy:’key’
It seems OrderBy isn’t working as expected
Example here
http://jsfiddle.net/mhXuW/

Solutions/Answers:

Solution 1:

The parameters to orderBy must match property names in an array of objects.

Your data needs to look something like this:

$scope.list2 = [ { id:"2013-01-08T00:00:00", name:'Joe'},
                 { id:"2013-01-09T00:00:00", name:'Sue'}];

Then a filter like this will work:

<div ng-repeat="item in list2 | orderBy:'id':true">

Fiddle.

Note that orderBy works on the entire array (something in your sample code above) and it returns a sorted array. orderBy doesn’t know anything about key and value.

Solution 2:

this is not implemented. please see here:

https://github.com/angular/angular.js/issues/1286

EDIT (Aug 27, 2013): this issue seems to be resolved now.

Solution 3:

I created a custom filter to make this possible for a select list:

.filter('toDictionaryArray', function () {
    return function (obj) {
        if (!(obj instanceof Object)) return obj;

        var arr = [];
        for (var key in obj) {
            arr.push({ key: key, value: obj[key] });
        }
        return arr;
    }
})

the select list ng-options then reads as follows:

<select ng-options="kv.key as kv.value for kv in p.options | toDictionaryArray | orderBy:'value'"></select>

Solution 4:

As akonsu says, the feature has been requested here. However, I still couldn’t get it to work with the latest (1.3.0) beta.

Related:  What is the correct terminology for a “non-AJAX” request?

There’s a nice workaround buried in the comments (note this requires underscore, and you’ll have to replace references to ‘key’ with ‘value.$key’ in the above example):

[Add a filter]:

app.filter('toArray', function() { return function(obj) {
    if (!(obj instanceof Object)) return obj;
    return _.map(obj, function(val, key) {
        return Object.defineProperty(val, '$key', {__proto__: null, value: key});
    });
}});

Example markup:

<div ng-repeat="val in object | toArray | orderBy:'priority' | filter:fieldSearch">
  {{val.$key}} : {{val}} 
</div>

Potential downsides:

  • Object.defineProperty will not work like this in IE8 (if you don’t
    mind making $key enumerable then this is an easy change).
  • If your property values are not objects then you can’t set the $key property
    and this fails.
  • It is not directly integrated into orderBy or ngRepeat
    so the syntax is different.

Solution 5:

Here’s a good work around (angular-toArrayFilter):

.filter('toArray', function () {
  return function (obj, addKey) {
    if ( addKey === false ) {
      return Object.keys(obj).map(function(key) {
        return obj[key];
      });
    } else {
      return Object.keys(obj).map(function (key) {
        if(typeof obj[key] == 'object') return Object.defineProperty(obj[key], '$key', {     enumerable: false, value: key});
      });
    }
  };
});

Solution 6:

Instead of using orderby filter which only supports arrays and not objects. You can write your own filter like this I took the code from Justin Klemm http://justinklemm.com/angularjs-filter-ordering-objects-ngrepeat/ and added support for specifying multiple ordering fields. The filter example below is to filter based on properties in the values but you can easily change it to filter based on the key as well.

app.filter('orderObjectBy', function()
{
    return function(items, fields, reverse)
    {
        var filtered = [];
        angular.forEach(items, function(item)
        {
            filtered.push(item);
        });

        filtered.sort(function (a, b)
        {
            var result = 0;

            for (var i = 0; i < fields.length; ++i)
            {
                if (a[fields[i]] > b[fields[i]])
                {
                    result = 1;
                    break;
                }
                else if (a[fields[i]] < b[fields[i]])
                {
                    result = -1;
                    break;
                }
            }

            return result;
        });

        if (reverse)
        {
            filtered.reverse();
        }

        return filtered;
    };
});

Then inside your html5 web page use it like so where statistics is a map/dictionary with key value pairs.

<div class="row" ng-repeat="stat in statistics | orderObjectBy: 'NumTimesPartnered', 'NumTimesOpponent']:true">
    <div class="col col-33">{{stat.Name}}</div>
    <div class="col"><center>{{stat.NumTimesPartnered}}</center></div>
    <div class="col"><center>{{stat.NumTimesOpponent}}</center></div>
</div>

References