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.
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.
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.
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.
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.
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.
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.
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.
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.
You can even sort columns in your table by different criteria by using a switch statement for the sortHeaderId.
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.