Friday, June 18, 2021

Implementing Custom Sorting on MatTable Columns in JavaScript

I couldn’t count the number of tables that I’ve worked on that required at least one sortable column. It’s the kind of thing that users love, perhaps because it gives them tremendous control over their data. In the early days of the Web, I used JavaScript to implement custom sorting. Now, there are many third-party widgets that have sorting capabilities baked in. In this regard, Angular is no exception. The Material Data Table, or MatTable, is chock-full of features, including pagination, sorting, filtering, to name but a few. This tutorial will describe how to give your users the ability to sort table columns using the MatTable’s built-in sorting mechanism.

Demo Preview

Some readers may be unaware that I am also a professional musician and recording artist. To combine my two passions, I created a table to track some of my digital release singles. Here’s a screen capture of the finished demo:

JavaScript tables

As you can see, it contains five columns, all but the Thumb column, are sortable. The current sort is indicated by an arrow to the right of the column header. By default, columns appear in the same order as in the underlying data source. I would like to sort table rows by Release Date in descending order, so that the most recent release appears at the top of the table, as they appear in the above screen shot. We’ll go over how to do that in the next section.

Setting the Initial Sort

The first step is to make table columns sortable. To do that, import the MatSort directive and add mat-sort-header to each column header cell that should trigger sorting:

<mat-table [dataSource]="dataSource" matSort>

MatSort is a container for MatSortables to manage the sort state and provide default sort parameters. We can reference it using the @ViewChild Property Decorator to access its properties. The initial sort is set in our table class’s OnInit hook:

import {Component, OnInit, ViewChild} from '@angular/core';
import {MatTableDataSource, MatSort, Sort} from '@angular/material';

export class TableSortingExample implements OnInit {
  public displayedColumns = [
    'name', 
    'coverOrOriginal', 
    'releaseDate',
    'length',
    'thumbnailUrl'
  ];
  public dataSource = new MatTableDataSource(RELEASES);
  @ViewChild(MatSort) private sort: MatSort;

  ngOnInit() {
    this.dataSource.sort = this.sort;

    const sortState: Sort = {
      active: 'releaseDate', 
      direction: 'desc'
    };
    this.sort.active = sortState.active;
    this.sort.direction = sortState.direction;
    this.sort.sortChange.emit(sortState);
  }
}

Making a Column Sortable

Once you’ve added sorting capability to your table using the instructions above, all you have to do to designate a column as sortable is add the mat-sort-header directive to the <mat-header-cell> element in the template. Here’s the name column:

  
<!-- name Column -->
<ng-container matColumnDef="name">
  <mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
  <mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
</ng-container>

Advanced Sorting

The MatTableDataSource sorts with the assumption that the sorted column’s name matches the data property name that the column displays. If the data properties do not match the column names, or if a more complex data property accessor is required, then you can employ a custom sortingDataAccessor function to override the default data accessor on the MatTableDataSource. Here’s an example that sorts a date column where a string was used instead of a Date type:

ngAfterViewInit() {
  this.dataSource.sortingDataAccessor = (item, property) => {
    switch (property) {
      case 'date': {
        return new Date(item.date);
      }
      default: {
        return item[property];
      }
    };
  }
}

If you are not using the MatTableDataSource, but instead, are providing a data array directly to the table, you can listen to the sort’s matSortChange event and re-order your data according to the sort state. Afterwards, you may have to invoke renderRows() on the table, since it will not automatically check the array for changes. Here‘s a table that contains nutritional information of deserts that uses the matSortChange hook to sort on the Carbs column:

<table matSort (matSortChange)="sortData($event)" matSortActive="carbs" matSortDirection="asc">

The sortData() function handles all of the sorting for the table:

public sortData(sort: Sort) {
  const data = this.desserts.slice();
  if (!sort.active || sort.direction === '') {
    this.sortedData = data;
    return;
  }

  this.sortedData = data.sort((a, b) => {
    const isAsc = sort.direction === 'asc';
    switch (sort.active) {
      case 'name': return compare(a.name, b.name, isAsc);
      case 'calories': return compare(a.calories, b.calories, isAsc);
      case 'fat': return compare(a.fat, b.fat, isAsc);
      case 'carbs': return compare(a.carbs, b.carbs, isAsc);
      case 'protein': return compare(a.protein, b.protein, isAsc);
      default: return 0;
    }
  });
}

function compare(a: number | string, b: number | string, isAsc: boolean) {
  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

Conclusion

In this tutorial we learned how to give your users the ability to sort table columns using the MatTable’s built-in sorting mechanism.


Rob Gravelle resides in Ottawa, Canada, and has been an IT Guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Popular Articles

Featured