Angular Material: Deep nested objects in sortable table

Danny Cornelisse
24-02-2019

How to use material sortable table (matSort) with nested objects

Angular material is a library that provides customizable components that can be used in your favourite Angular app. However, in my opinion, the API for the components is quite in-flexible. For instance, the material sortable table cannot handle nested objects by default. Let’s assume the following data structure:

const ELEMENT_DATA: PeriodicElement[] = [
  {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
  {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
]

This will work just fine and the table is perfectly sortable (by column value). However, when adding one level of complexity, the table breaks and is unsortable:

const ELEMENT_DATA: PeriodicElement[] = [
  {position: 1, name: 'Hydrogen', weight: { value:1.0079 }, symbol: 'H'},
  {position: 2, name: 'Helium', weight: { value: 4.0026 }, symbol: 'He'},
]

The solution to this is to change the sorting data accessor of the material table data source:

// Initialize dataSource 
dataSource: MatTableDataSource = new MatTableDataSource();

// Define ViewChild. Should be in template!
@ViewChild(MatSort) sort: MatSort;

  /**
   * Based on comment in stack overflow thread:
   * https://stackoverflow.com/questions/48891174/angular-material-2-datatable-sorting-with-nested-objects#answer-52938020
   * NOTE: @ViewChild won't work when viewchild is wrapped by *ngIf directive within the template associated
   * to this component. Any other (parent) component implementing the *ngIf directive around this component is fine
   * https://stackoverflow.com/questions/34947154/angular-2-viewchild-annotation-returns-undefined
   */
  sortingDataAccessor(item, property) {
    if (property.includes('.')) {
      return property.split('.')
        .reduce((object, key) => object[key], item);
    }
    return item[property];
  }

/**
* Assign the new sortingDataAccessor to replace the default sortingDataAccessor!
*/
  ngOnInit() {
    this.displayedColumns = ['position', 'name', 'weight.value', 'symbol'];
    this.dataSource.sortingDataAccessor = this.sortingDataAccessor;
    this.dataSource.sort = this.sort;
    this.dataSource.data = ELEMENT_DATA;
  }


As you can see in the comments, I took this solution and adapted it to my own needs. To explain, the material table requires the following attribute to know which columns to display:

*matRowDef="let row; columns: displayedColumns;

This references the displayedColumns member of your component. To be able to sort by header, each header needs the following attributes:

 matColumnDef="name"

The matColumnDef is used to make clear which attribute to sort on. This should be exactly the same as in displayedColumns! Now, if you want to sort on weight.value, the item in displayedColumns should be the same as in matColumnDef: ‘weight.value’. Once you click on the weight table header, your new sortingDataAccessor method will take over. Because you separated the matColumnDef by periods (‘.’), this function will reduce this string to an object reference: element['weight']['value']. This is javascript! The native sort function of MatTableDataSource now knows to sort on the value attribute of weight!

Working example

You can nest your objects as deep as you want and you can still sort them using the new sortingDataAccessor, as long as your matColumnDef matches a string in displayedColumns. However, the more I work with Angular Material, the more I dislike the inflexibility of its components. The inflexibility makes it hard to implement own functionality or extend native material functionality, like done in this post.

Still, I hope you learned something.

Cheers

LEAVE A REPLY

3 Comments

  • Wasi Ullah Khan says:

    You Sir are a legend. This article really helped me in solving my nested data sorting. Respect for you. God bless you. Thank you.

  • Alex Blank says:

    In case the nested property is missing in one of the data rows the sortingDataAccessor throws an error. I replaced the reduce part with…

    .reduce((object, key) => object[key] || '', obj);

    …and the table also sorts empty properties. At the beginning when ascending, at the end when descending.

    • IndyJones72 says:

      It also sorts by case sensitivity. Is there anyway to prevent this? I tried to check to make sure it wasn’t null and then return the string as lower case, but after clicking the sort arrow twice I started getting “toLowerCase is not a function”. So I’m close, but obviously missing something else.

you might also like