Angular Material Table: sort complex objects using MatTableDataSource

Updated: 2023-12-18

The simple scenario, mapping and sorting out of the box

In this example we will show how to sort complex objects in an Angular Material Table.

When we use MatTableDataSource in Angular Material, Angular out of the box matches the columns with the property of our object.

Example, we want to show a list of students:

The student is a simple object with 2 properties: name and age.

export interface Student { 
    name: string; 
    age: number; 
} 

In the Angular component we retrieve a list of students from a service and we assign them to the DataSource:

@ViewChild(MatSort) sort!: MatSort; 
displayedColumns: string[] = ['name', 'age']; 
dataSource: MatTableDataSource<Student> = new MatTableDataSource(students); 
 
private initTable(): void { 
    this.service.loadStudents().pipe( 
        first(), 
        map(datasource => { 
            this.dataSource.data = datasource; 
            this.dataSource.sort = this.sort; 
        }) 
    ).subscribe(); 
} 
 

Our HTML table can manage the sorting for us without any configuration.

Angular maps the properties of the object student with the columns name and age.

<table mat-table [dataSource]="dataSource" [trackBy]="..."]> 
    <ng-container matColumnDef="name"> 
        <th mat-header-cell *matHeaderCellDef mat-sort-header> Name </th> 
        <td mat-cell *matCellDef="let element"> {{element.name}} </td> 
    </ng-container> 
    <ng-container matColumnDef="age"> 
        <th mat-header-cell *matHeaderCellDef mat-sort-header> Age </th> 
        <td mat-cell *matCellDef="let element"> {{element.age}} </td> 
    </ng-container> 
     
    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> 
    <tr mat-row *matRowDef="let row; columns: displayedColumns; let i = index;"></tr> 
</table> 

The complex scenario, a structured object with nested properties

If our object is more complex Angular cannot automatically map the properties with the columns.

Example:

export interface Student { 
    name: string; 
    age: number; 
    address: Address; 
} 
 
export interface Address { 
    street: string; 
    city: string; 
} 

In this case we have to define a SortingDataAccessor to tell Angular which field has to return for a given column name.

From the documentation:

sortingDataAccessor: (data: T, sortHeaderId: string) => string | number

Data accessor function that is used for accessing data properties for sorting through the default sortData function. This default function assumes that the sort header IDs (which defaults to the column name) matches the data's properties (e.g. column Xyz represents data['Xyz']). May be set to a custom function for different behavior.

In our component we have to define a sortingDataAccessor function:

sortingDataAccessor: (data: Student, property: string) : string | number => { 
    switch (property) { 
        case 'name': return data.name; 
        case 'age': return data.age; 
        case 'street': return data.address.street; 
        case 'city': return data.address.city; 
        default: return data[property]; 
    } 
} 

Our displayedColumns array becomes: displayedColumns: ['name', 'age', 'street', 'city']

During the initialization of the table we have to assign the sortingDataAccessor:

this.dataSource.sortingDataAccessor = this.sortingDataAccessor; 

The HTML as to be updated too with the new columns.

Now your 2 dimensional table can represent a list of nested objects.


You could be interested in

Right click context menu with Angular

Right click custom menu inside dynamic lists with Angular Material
2020-05-31

Enums in Angular templates

How to use enum in the html template
2019-01-21
WebApp built by Marco using SpringBoot 3.2.4 and Java 21, in a Server in Switzerland without 'Cloud'.