Tuesday, October 29, 2013

AngularJS table sort

I've had a chance to play around with AngularJS recently. The first thing I came up against was having a table where you could sort each column by clicking the header. At first I did all sorts of crazy things like writing my own directive until I stumbled upon the orderBy filter.

The first thing I found out is that you need to keep it simple by calling a property on an object - using getter functions won't quite work. But knowing that we can take a look at the orderBy example in the AngularJS documentation. It looks handy but there is a problem around how reverse works:

<div ng-controller="Ctrl">
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
<hr/>
[ <a href="" ng-click="predicate=''">unsorted</a> ]
<table class="friend">
<tr>
  <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a>
    (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th>
  <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th>
  <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th>
</tr>
<tr ng-repeat="friend in friends | orderBy:predicate:reverse">
  <td>{{friend.name}}</td>
  <td>{{friend.phone}}</td>
  <td>{{friend.age}}</td>
</tr>
</table>
</div>


In the example we can view "name" normally by clicking it or in reverse by clicking on the arrow. I wanted it so that clicking again would reverse the order. It looks like phone and age do this, but can you spot the problem? the issue is they both change reverse, so if I click on phone then things will be reversed and clicking on age will give me age without reverse. What I needed was that clicking a column the first time would reset reverse, so how do we go about that?

<div ng-controller="Ctrl">
<pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre>
<hr/>
[ <a href="" ng-click="predicate=''">unsorted</a> ]
<table class="friend">
<tr>
  <th><a href="" ng-click="reverse = predicate == 'name' && !reverse; predicate = 'name'">Name</a></th>
  <th><a href="" ng-click="reverse = predicate == 'phone' && !reverse; predicate = 'phone'">Phone Number</a></th>
  <th><a href="" ng-click="reverse = predicate == 'age' && !reverse; predicate = 'age'">Age</a></th>
</tr>
<tr ng-repeat="friend in friends | orderBy:predicate:reverse">
  <td>{{friend.name}}</td>
  <td>{{friend.phone}}</td>
  <td>{{friend.age}}</td>
</tr>
</table>
</div>

The magic happens with ng-click and we use && to work as a control statement. Because it's an && it will only continue if the first expression is true, in this case we check to see if we're already sorting by that column. If we're not sorting by the column then we return false - so clicking on a new column will put reverse as false. The second part will then flip reverse for us if we are already sorted by that column.

Last thing to note is that we have set reverse but still need to set what we're sorting by so we put that last (so it doesn't get in the way of checking if we're already set at the column).

And there we have it, the expected behaviour without the need of a new directive or controller.

8 comments:

  1. Looks good! I think it's worth mentioning that if you don't need to support IE <= 8, you can also use Object.defineProperty to define a computed property with the effect of a getter function that works with orderBy.

    ReplyDelete
  2. This is brilliant. I went a step further and setup two functions for ng-click and ng-class (class to set the ascending/descending styles on the headers) to clean up the code a bit.

    ReplyDelete
    Replies
    1. can u share the code i am struggling to put the asc and dsc styles on teh headers

      Delete
  3. Isn't it more easy like this:
    ng-click="reverse = !reverse; predicate = 'distance';

    ReplyDelete
    Replies
    1. no, the problem with that is that reverse gets remembered when you change column when you really want it reset.
      so let's say you setup with predicate a & b with a default of a and reverse = false. Now I click b, reverse becomes true and I see b reversed when I really want it to not be reversed the first time I click a column.

      Delete
  4. Danny, can you share your class code for the up and down arrows please

    ReplyDelete