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.

  <li ng-repeat="item in items">{{ item }}</li>

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.

  <li ng-repeat="item in items | orderBy:'color'">{{ item.color }}</li>

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.

  <li ng-repeat="(key, item) in items | orderBy:'color'">{{ item.color }}</li>

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.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="item in items | orderObjectBy:'color':true">{{ item.color }}</li>

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.



  • Thanks for this. I ran into the same problem. Coming from a PHP background, JavaScripts differences with “associate arrays” have been killing me. With my AngularJS app, I gave up with the associative array ordering properly.

    Now you’ve shown I should have dug a bit deeper.

  • Jacob Evan Shreve

    Thanks! I was getting very frustrated with orderBy not working on my object of objects, and not getting any error feedback.

  • Adam Brooks

    Much appreciated Justin!

  • park seung hyun

    i add that “sort by key”

    • Thomas Waldecker

      Thanks for sharing!

  • Michael Taranto

    This was a great solution. My only issue I had was that it worked in Chrome but failed in my unit tests which were being run in PhantomJS. I narrowed it down to the compare function that gets passed to sort returning a boolean instead of 1, 0, or -1.

    So rather than
    filtered.sort(function (a, b) {
    return (a[field] > b[field]);

    I changed it to
    filtered.sort(function (a, b) {
    return (a[field] > b[field]) ? 1 : ((a[field] < b[field]) ? -1 : 0);

    Now it works everywhere and passes in my unit tests too

    • Michael, thanks for the heads up on this! I’ll do some testing and look at changing the code in the post.

  • Nir Melamoud

    Thanks, you are the best!

  • thallisphp

    Thanks, you saved my night =D

  • Thanks Justin. Any idea, how this could make it into AngularJS?

  • Logan

    Exactly what I was looking for while trying to sort a collection. I needed the key, but fortunately, I had this available as an attribute in the collection, so I just used {{item[0].key}} in my view. Works great! Thank you!

    • Logan, glad it was useful! Note that if you end up needing the key in a collection where it’s not available, a developer commented further down with a solution that adds it to each item:

      • Logan

        Good to know!

      • Alexander Nyquist

        As I see it, the more angular-way would be to name it $key instead of just key.

  • Bartlomiej Skwira

    Here is a CoffeeScript version I used

    yourApp.filter “orderObjectBy”, ->

    (items, field, reverse) ->

    filtered = []

    angular.forEach items, (item) ->

    filtered.push item

    filtered.sort (a, b) ->

    a[field] > b[field]

    filtered.reverse() if reverse


    later also user the ‘key’ version modified to id (thats the key in my collection)

    myApp.filter “orderObjectBy”, ->

    (items, field, reverse) ->

    filtered = []

    angular.forEach items, (item, key) ->

    item[‘id’] = key

    filtered.push item

    filtered.sort (a, b) ->

    a[field] – b[field]

    filtered.reverse() if reverse


  • Plasmed

    Thanks a lot!

  • This is Killer.

    Useful 🙂

    Thanks Justin!

  • Pachito Marco Calabrese


  • Claus Colloseus

    Another, even shorter solution would be to convert the object into an array before you start to repeat:
    ng-repeat=”item in itemsToArray(items) | orderBy: ‘color'”


    $scope.itemsToArray = function (items) {
    var array = [];
    angular.forEach($scope.words, function(item) {
    return array;


  • This code was sorting last/first element wrong for some reason. Used toArray filter. Usage: ng-repeat=”x in y | toArray | orderBy:’color’:true”
    app.filter(‘toArray’, function () {
    ‘use strict’;

    return function (obj) {
    if (!(obj instanceof Object)) {
    return obj;

    return Object.keys(obj).map(function (key) {
    return Object.defineProperty(obj[key], ‘$key’, {__proto__: null, value: key});

  • George Bora

    Thanks, I was stuck on why orderBy didn’t work and you provided both a explanation and a solution.

  • How can I set a default value and not get the empty option? Like when I’m using a hash of hashes object data source. Thanks, this post helped me a lot.

    • I’m not totally sure I know what you mean… but if you’re asking about showing some other element if the list if empty, you could try adding something like this below the

      No entries

  • jorenm

    Here’s a version I wrote for coffeescript. A bit modified so it accepts an array of keys, so you can filter to an arbitrary point in a nested object.

  • Janne

    This was a great post and a good solution, thank you for this! The only problem for me was that I need the indexes of the original object to be used in the template. Like: { “JH937NSK”: { data: 0 }, “VNDK9977S”: { data: 1 } }.

    So I changed it a bit, to return object that holds both:

    return function(items, field, reverse) {
    var helper = [];
    angular.forEach(items, function(item, index) {
    helper[item[field]] = { “item”: item, “index”: index };

    if(reverse) helper.reverse();

    return helper;

    Which can be used in the template via obj.item and obj.index references.

    This is just kinda tacky / messy I think, because it changes the object being ordered. I didn’t find a better solution, since objects can not be ordered reliably (I even tried to create a new object in different order, but it didn’t work in chrome at least and always ordered it alphabetically). If someone has a better solution, please bring that on the table too :).

    • jorenm

      You definitely don’t want to change the object being ordered in an orderBy filter. I think perhaps what you want is this:

      • Janne

        Well the thing is that, even the original script already changes the object being ordered :). Since it transforms the object to an array, it will not receive the original indexes:

        Having a data like this: datas = { “JH937NSK”: { data: 0 }, “VNDK9977S”: { data: 1 } }

        after being processed through: ng-repeat=”data in datas | orderObjectBy:’location'”

        Filter would produce: [ { data: 0 }, { data: 1 } ] . So in this case the indexes are: 0 and 1, not “JH937NSK” and “VNDK9977S” as they should be.

        So I need the original index, which gets lost after orderObjectBy-filter has processed the data.

        The filter that I posted, seems to cause an infDig-error.

        • jorenm

          I see what your issue is now. The infDig error is the result of changing the original object in the filter. The object you’re ng-repeating on is watched by angular, and when it changes, it runs the filter. The problem arises because you’re triggering the change listener inside the filter, causing infinite recursion.

          My code doesn’t change the original object, it simply returns an ordered array version of that object. Here’s a new gist that I think has what you want. I didn’t test it, but you should get the idea. It preserves the key you wanted, without modifying the original object.

          • Janne

            Thank you for the help! I actually still have the problem, that if I change the original object at all, like you wrote: filtered.push({key: key, value: item}), it will go to a loop.

            Eventhough the actual problem is not solved, I can still work around the problem and insert a .id-property to the original objects (which corresponds to the key / index) – the bad side is that I have to keep the index and the .id-property in sync for the program to work correctly.

  • franci

    If you need the keys, add <>

    angular.forEach(items, function(item) {

    angular.forEach(items, function(item) {

    • franci

      I forgot this:
      angular.forEach(items, function(item, key) {

  • Thanks for your filter! Works very good

  • Ariel Fuggini

    Thanks! Great tip

  • Nice, useful and simple. You saved my day. Thanks!

  • J. Kurian

    Thanks for the example, but, my array contains object with ids that I’m trying to sort by a status string and the problem is the ids are getting switched around for some reason.

    Working on a Gist to share…

  • leticia


  • Thanks so much for this.

  • Great article. Thank you for sharing. 🙂

  • grafox

    Thanks Bro … you make me Happy.

  • Awesome post!

    For case-insensitive sort, I just changed this line…
    return (a[field].toLowerCase() > b[field].toLowerCase() ? 1 : -1);

    • Stuart

      this didn’t work for me. i get ‘a[field].toLowerCase is not a function’ error. any ideas?

      • stuart

        never mind! i had to add .toString() infront of .toLowerCase(). i guess some of my property values aren’t strings.

        • stuart

          i added type detection in mine for mixed data types on the properties, to get case insensitive sorting on string.

          if (typeof a[field] == ‘string’) {

          return (a[field].toString().toLowerCase() > b[field].toString().toLowerCase() ? 1 : -1);

          } else {

          return (a[field] > b[field] ? 1 : -1);


  • Alex Gutierrez

    Thanks, it worked for me.

  • Peter Barraud

    life saver

  • Glogo

    Thanks, worked for me without issues

  • Jeffry

    I think this code can’t work on angularjs 1.3.8, i already write the same code…..

  • Thank you very much, works super well.

  • SSwitzer

    Thanks — this was a life saver… even without the code solution, the key piece (“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”) did the trick.

  • Kimberly Newell

    I used lodash _.sortBy to solve this issue.

  • Andrei

    Life saver!

  • Vasyl

    Good solution!

    Justin, could you please put information about the license ?

    Thank you!

    • Hi Vasyl, I’ve added an MIT license to the code in the article.

      • Vasyl

        Thank you!

  • Pablo Allendes

    Thanks!, ngOrderObjectBy was exactly what i was looking for! Works Perfect

  • Stephen Ingram

    Quick thought, (untested code)

    .filter(‘orderObjectBy’, function orderObjectByFactory($filter) {
    return function orderObjectBy(object, sortPredicate, reverseOrder) {
    return $filter(‘orderBy’)(
    Object.keys(object).map(function(key) {
    return object[key]

  • Björn Ali Göransson

    I prefer splitting the object into an array of key-value pairs, so you can use the regular orderBy.

    result =, function (value, key) {
    return { key: key, value: value };

  • williamknn

    When I try to use orderObjectBy: ‘date’ it breaks all my dates “31/12/1969”.

  • Eduardo Russo

    Awesome!!! This helped me a lot.

  • Kris

    I need to just say… you rock man! Found a bug in new code I inherited that used 1.4 code and I’m trying to move to 1.5 and for some reason it worked in 1.4 for objects… but not in 1.5! This fixed it 🙂

  • Rafael Gomes Francisco

    It is GREAT!
    Thanks for help me!

  • Alex Mills

    Good news!! If you have an object/hash, you do not need to convert to an array. In 2016 and beyond, JS object keys are ordered by insertion order. And you can always use an ES2015 Map object if you are really worried about it. We have an Angular1.6 app and we converted our ng-repeat from an array to an object. And then I was like Oh Fuck, what about sorting/ordering. I did *not* want to convert back to array. Luckily, we can use this algorithm to do sorting/ordering of a hash/object/map:

  • Gladson Bruno

    Thank you, that’s exactly what I was looking for.