Using orderBy to sort Date strings in AngularJS

Suppose you have an array or list that includes date strings, and you want to apply the AngularJS orderBy filter. This is usually fine for dates in the format YYYY-mm-dd as they end up sorted more or less correctly. However, other formats can give you a headache. The format dd-mm-YYYY doesn’t sort as nicely, neither does mm-dd-YYYY. This article provides two possible solutions for orderBy while keeping the data as a string. The first approach will use a sort function and the second approach will use a comparator function.

The Problem

Let’s take a look at an example using an array of objects called data which containing a list of student names and start dates.

var app = angular.module('myApp', []);
app.controller('myController', function($scope) {
    $scope.data = [
        {'name': 'Dean', 'start_date': '03-05-2019'},
        {'name': 'Marvin', 'start_date': '17-11-2017'},
        {'name': 'Sally', 'start_date': '25-04-2018'},
        {'name': 'Amber', 'start_date': '12-09-2017'},
        {'name': 'George', 'start_date': '30-05-2019'},
    ];
});

The following HTML is used to render the data in a table using ng-repeat. The orderBy filter is used to sort by the start_date:

<div ng-app="myApp">
  <div ng-controller="myController">
    <h1>Students</h1>
    <table class="table table-dark table-striped">
      <thead class="thead-dark">
        <tr>
          <th>Name</th><th ng-click="">Start date</th>
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat="row in data | orderBy:'start_date'">
          <td>{{row.name}}</td><td>{{row.start_date}}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Since the start_date is actually a string, it is sorted as a string. This is the unfortunate result:

Date strings incorrectly sorted

Because we are using the format dd-mm-YYYY, the outcome is the days are sorted first, then the months, then the years. Notice that in the above screenshot, the dd component is sorted as we would expect for a string: 03, 12, 17, 25, 30

Solution 1: use a sort function

The first option involves re-formatting the start_date in a sort function. This is fairly straight forward, since we can keep start_date as a string and simply reverse the order from dd-mm-YYYY to YYYY-mm-dd, and then the sort will work:

We’ll do this in a couple of steps so it’s easier to follow. First, create a new function to help us sort rows by the start_date field.

$scope.sortDates = function(row) {
    return row['start_date'];
};

Next, modify the ng-repeat filter to call the function:

<tr ng-repeat="row in data | orderBy:sortDates">
  <td>{{row.name}}</td><td>{{row.start_date}}</td>
</tr>

At this point, nothing has changed. The start_date is still being treated as a string and sorted incorrectly.

Now, flip the start_date from dd-mm-YYYY to YYYY-mm-dd format in the sort function:

$scope.sortDates = function(row) {
    return row['start_date'].split('-').reverse().join('-');
};

The sorted data should be looking a bit better now. Note that the displayed start_date values are still in dd-mm-YYYY format:

Date strings correctly sorted

Solution 2: use a comparator function

Another option is to use a comparator function. This is the original orderBy filter using the start_date attribute:

<tr ng-repeat="row in data | orderBy:'start_date'">

To include a comparator function you need to ensure you also include the reverse argument which controls the sort order. I’ll use false for a default ascending sort. This is followed by the function name:

<tr ng-repeat="row in data | orderBy:'start_date':false:compareDates">

Now create the compareDates function. A comparator function will accept 2 arguments, which are the values to be compared. For now, let’s just print the first parameter to the console to see what we’re getting:

$scope.compareDates = function(val1, val2) {
    console.log(val1);  
};

In the console log you will see that val1 is actually an object:

Object { value: "03-05-2019", type: "string", index: 0 }

There are three properties:

  • value - the value to be compared, in this example, it is the start_date value
  • type - the variable type. This confirms our date is actually a string, and is useful to know if you need to deal with a variety of data types.
  • index - the index of the row being compared

So the comparator function will need to compare val1.value against val2.value.

The next thing to be aware of is that the comparator function should return -1 or 1 to denote the result of the comparison.

As a quick example, you could use the Javascript String method localeCompare(). This would produce the same results we had initially, i.e. the dates are sorted incorrectly as a string:

$scope.compareDates = function(val1, val2) {
    return (val1.value.localeCompare(val2.value));
};

Date strings incorrectly sorted

There are a few different ways we can compare the date strings, but let’s finish off this example by converting each string to a Date() and comparing them:

$scope.compareDates = function(val1, val2) {
    return (strToDate(val1.value) <= strToDate(val2.value)) ? -1 : 1;
};
function strToDate(dateStr) {
    var dateArray = dateStr.split('-');
    return new Date(dateArray[2], dateArray[1] - 1, dateArray[0]);
};

The start_date should now be sorted correctly:

Date strings correctly sorted

Further reading

The AngularJS documentation provides a detailed explanation of orderBy, including a section dedicated to comparators: https://docs.angularjs.org/api/ng/filter/orderBy