Sorting an Angular Material table - how to use MatSort and sortingDataAccessors mat sort app

Sorting an Angular Material table – how to use MatSort and sortingDataAccessors

A key feature in many applications is the ability to sort tabular data according to the users preferences. Angular Material provides the MatSort directive as an easy way the include interactive sorting in the tables of your application. With the MatSort directive, the user can sort the data on different columns by clicking on the column header. We use the MatSort directive in several of our projects, as it is an easy way to add sorting to Material tables without having to write custom sorting functions.

However, sometimes you need sorting that is a little more advanced. For instance, you may need to sort on properties that are nested in an object or you don’t want to sort by the default sort order. In these cases custom sortingDataAccessors can manipulate the way MatSort sorts your columns.

In this blogpost I will show the best way to add basic sorting to your Material table. Additionally, I will show how to use custom sortingDataAccessors to sort on nested properties and case insensitively. Lastly, I will describe how to combine different sorting requirements.

Where to find the code examples

All code examples in this article are available in a demo project on github. For this demo project I created a new angular app with the angular cli (see the Angular documentation on how to do that) and added Angular Material (see Angular Material documentation). It is a very simple application with just one page that contains an Angular Material table.

A screenshot of the demo project available on github

Add sorting to a Material table

Adding sorting to a material table in an angular application is relatively simple. First, you need to import the MatSortModule into the module where (the component with) your table is located. Next, you need to add the MatSort directive to the table element. Optionally, you can add an initial sorting column and direction to the MatSort directive. In addition, you have to add the mat-sort-header component to the headers of the sortable columns.

HTML snippet of the table element and one of the columns including the MatSort directive and mat-sort-header
The table with the MatSort directive and the header with the mat-sort-header component

In the component (typeScript file), you need to add the MatSort to the dataSource with a viewChild. According to the example in the Angular Material documentation for MatSort, you should do this in the AfterViewInit lifecycle hook. The viewChild will query the view and the result will be added to dataSource. By doing this in AfterViewInit you make sure the view, and thus the MatSort directive, is present when it is added to the dataSource.

This solution will work fine, unless your table is not rendered immediately when your view is rendered. This will for example happen when you have an *ngIf directive on your table or one of it’s parent elements. If you try to add the MatSort to the dataSource when the MatSort directive is not yet rendered in your view, the sorting of your table will not work! A better solution is to add the sort to the dataSource with a setter. This will ensure the sort is added to the dataSource at the moment it is set in the view and ,therefore, present.

TypeScript snippet of the setter for MatSort
A setter to add MatSort to the dataSource in the component

Mat-sort-header component

By default, the mat sort header will use the column id (supplied in MatColumnDef) as the property to sort on. For example, sorting on the id column, shown in the image below, will work by just putting mat-sort-header on the header.

HTML snippet of a column with a default mat-sort-header
The mat-sort-header will sort on the property name supplied in matColumnDef by default

You can sort on a different property than the value in the matColumnDef by assigning a value to the mat-sort-header. For example, the column id for the column below is “username”, but the sorting will be done on the “name” property when it is added to the mat-sort-header as a value.

HTML snippet of a column with a mat-sort-header with a value different from the column id
Sorting can be done on a different property by adding a value to the mat-sort-header

This means you can also sort on a completely different property than the one that is shown in the column, by putting a different value in the mat-sort-header. This could be useful, for example, if you want to show an icon in the table but need to sort on a numeric value.

Sorting on nested properties

Often your data will contain objects within objects. For example, if you have an array with user data, one of the properties could be “address” which will contain several other properties such as “streetName” and “postalCode”. Sorting these nested properties will not work “out of the box”. If you provide the mat-sort-header with the value “streetName” it will try to find the property “user.streetName”. Additionally, providing the value “address.streetName” will also not work, since the mat-sort-header will not understand that you mean a to access a nested property.

Luckily, you can customize the way the mat-sort-header gets the “properties to be sorted” by providing a custom sortingDataAccessor to the dataSource. A sortingDataAccessor needs to have to following type signature: sortingDataAccessor: ((data: T, sortHeaderId: string) => string | number);, where T is the type of the data provided to the dataSource.

If you want to use the dot notation, like “address.streetName”, for nested properties you will need a custom sortingDataAccessor that will be able to identify the separate properties and return the value of the most nested property. I got the function to access nested properties from this stackoverflow post.

TypeScript snippet of a custom sortingDataAccessor for nested properties
A custom sortingDataAccessor to get nested properties from the data

In the function shown above, the sortingDataAccessor splits the sortHeaderId (the value provided to mat-sort-header) on the dot and uses a reduce function to subsequently access each nested property. This function will also work for non-nested properties. Then the array result from the split function will simply contain one property name and the corresponding value will be returned. If a property name does not exist no errors will occur. The function will return undefined as value and the sorting for this column will not work.

Now the only additional step is to set the nestedProperty sortingDataAccessor as sortingDataAccessor on the dataSource. You can do this in the setter for MatSort, but doing this in the constructor is also possible. This is possible because the dataSource and sortingDataAccessor are both already defined when the constructor is called.

TypeScript snippet showing how to add the sortingDataAccessor to the dataSource
The custom sortingDataAccessor needs to be added to the dataSource

Sorting case insensitively

You can also use a sortingDataAccessors to transform values before sorting is applied. In this way you can adjust the order of the sorting and the way the sorting is done. For example, by default, strings are sorted with all capital letters first, followed by all lowercase letters. If you want to sort case insensitively you can transform the values to all uppercase or lowercase letters.

TypeScript snippet of a custom sortingDataAccessor for case insensitive sorting
A custom sortingDataAccessor for sorting case insensitively

The data in your original array is not affected by this transformation. The transformation is only used for sorting.

In this way, all sorts of transformations can be done to your data. If, for example, you want to sort on the last letter of each value, you could write a function to reverse the characters in a string before sorting.

Combining multiple custom sortingDataAccessors

You cannot combine multiple sortingDataAccessors by simple chaining the functions. The expected input of a sortingDataAccessor is the object that contains the property and the property identifier, while the output is only the value of the property. However, you can combine multiple functions into one new sortingDataAccessor.

TypeScript snippet of a custom sortingDataAccessor that combines sorting on nested properties and case insensitive sorting
A custom sortingDataAccessor that combines sorting on nested properties and sorting case insensitively

You can even sort columns in your table by different criteria by using a switch statement for the sortHeaderId.

TypeScript snippet of the use of a switch statement for different sorting on indivicual columns
A switch statement on sortHeaderId can be used to apply different sorting to columns of your table

In this function you don’t necessarily need to point to a generic sortingDataAccessor. You can write any code here to return the value you want to sort on. In this way you can customize the sorting of your material table entirely to your needs.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.