Sorting arrays in Ember.js by various criteria

05 March 2014

I concluded the previous post on route refactoring by saying that the fact that the artists.songs route now has the songs as its model will pay dividends in the future.

I will show one thing that becomes really easy this way and also one less-known Ember feature that builds on top of that.

Showing list items in a certain order

Even with our superb routing, songs for an artist appear in random order, differently at each load of the application. This is confusing and it is now easy to remedy.

The controller backing the artist.songs template is now an ArrayController so establishing a sort order is a piece of cake:

1
2
3
4
5
6
App.ArtistSongsController = Ember.ArrayController.extend({
  (...)
  sortAscending: false,
  sortProperties: ['rating', 'title'],
  (...)
});

What I say with these sorting-related definitions is that I want songs to be shown in descending order of first their ratings and then -should the rating be the same- by title. That’s all there is to it, those tunes are now aptly sorted:

Ascending ordering

Sorting by multiple properties, in different directions

There is a slight deficiency in the above sorting API. It cannot sort multiple properties if the sorting is to be done ascending for some properties and descending for others. The sortAscending flag is “global” for the whole sortProperties array.

Taking the above example, it is a perfectly valid request to have songs first ordered in descending order of their rating and then in ascending order of their title. But how can we do that?

Reduce computed macros

The Ember.computed.sort macro in the reduce_computed_macros package provides a clean way to do exactly that:

1
2
3
4
5
6
App.ArtistSongsController = Ember.ArrayController.extend({
  (...)
  sortProperties: ['rating:desc', 'title:asc'],
  sortedSongs: Ember.computed.sort('model', 'sortProperties'),
  (...)
});

The API is so explicit it does not need any explanation. In order for the songs to appear in the specified order, we now need to iterate through sortedSongs so the #each in the template becomes #each sortedSongs.

Let’s take a look at how it has changed the order of the songs:

Perfect ordering

Perfect, I would say that is the ordering that makes most sense.

Wait, it gets better still

When first playing with Ember.computed.sort, my initial try was to set up sortedSongs like that:

1
  sortedSongs: Ember.computed.sort('model', ['rating:desc', 'title:asc']),

That, however, throws an error. Taking a look at the code and the tests, I realized that the second argument needs to be a property on the same object the sorting is defined on. This seems “over-engineered” at first but in fact opens up further possibilities.

Under the hood, Ember.computed.sort sets up an observer not only to the array being sorted but also to the sort properties. That way, when the sorting criteria changes, the list is reordered and rerendered automatically. This makes it possible for the criteria to be easily modified via user action.

To give you an example, I’ll make a button set the rating to happen only by title, in ascending alphabetical order:

1
2
3
4
5
6
7
8
9
<script type="text/x-handlebars" data-template-name="artist/songs">
  {{#if canCreateSong}}
    <div class="list-group-item">
      (...)
      <button class="btn btn-link pull-right" {{action "sortBy" "title:asc"}}>Sort by title</button>
    </div>
  {{/if}}
  (...)
</script>

The sortBy action handler sets the passed string as the value for the sortProperties which will rearrange the list:

1
2
3
4
5
6
7
8
9
10
11
12
App.ArtistSongsController = Ember.ArrayController.extend({
  (...)
  sortProperties: ['rating:desc', 'title:asc'],
  sortedSongs: Ember.computed.sort('model', 'sortProperties'),

  actions: {
    sortBy: function(sortProperties) {
      this.set('sortProperties', [sortProperties]);
    },
    (...)
  }
});

Indeed when the link is clicked, the songs become ordered by title only:

Ordering by only title

Declarative sorting

Let me finish by pointing out that this arrangment makes it possible to have several actions that set (or modify) the ordering criteria, without having to write a single line of code to implement the sorting itself.

You gotta love this.