Angular Material 2 DataTable Sorting with nested objects

51,675

Solution 1

It was hard to find documentation on this, but it is possible by using sortingDataAccessor and a switch statement. For example:

@ViewChild(MatSort) sort: MatSort;

ngOnInit() {
  this.dataSource = new MatTableDataSource(yourData);
  this.dataSource.sortingDataAccessor = (item, property) => {
    switch(property) {
      case 'project.name': return item.project.name;
      default: return item[property];
    }
  };
  this.dataSource.sort = sort;
}

Solution 2

You can write a function in component to get deeply property from object. Then use it in dataSource.sortingDataAccessor like below

getProperty = (obj, path) => (
  path.split('.').reduce((o, p) => o && o[p], obj)
)

ngOnInit() {
  this.dataSource = new MatTableDataSource(yourData);
  this.dataSource.sortingDataAccessor = (obj, property) => this.getProperty(obj, property);
  this.dataSource.sort = sort;
}

columnDefs = [
  {name: 'project.name', title: 'Project Name'},
  {name: 'position', title: 'Position'},
  {name: 'name', title: 'Name'},
  {name: 'test', title: 'Test'},
  {name: 'symbol', title: 'Symbol'}
];

And in html

<ng-container *ngFor="let col of columnDefs" [matColumnDef]="col.name">
      <mat-header-cell *matHeaderCellDef>{{ col.title }}</mat-header-cell>
      <mat-cell *matCellDef="let row">
        {{ getProperty(row, col.name) }}
      </mat-cell>
  </ng-container>

Solution 3

The answer as given can even be shortened, no switch required, as long as you use the dot notation for the fields.

ngOnInit() {
  this.dataSource = new MatTableDataSource(yourData);

  this.dataSource.sortingDataAccessor = (item, property) => {
     if (property.includes('.')) return property.split('.').reduce((o,i)=>o[i], item)
     return item[property];
  };

  this.dataSource.sort = sort;
}

Solution 4

I use a generic method which allows you to use a dot.seperated.path with mat-sort-header or matColumnDef. This fails silently returning undefined if it cannot find the property dictated by the path.

function pathDataAccessor(item: any, path: string): any {
  return path.split('.')
    .reduce((accumulator: any, key: string) => {
      return accumulator ? accumulator[key] : undefined;
    }, item);
}

You just need to set the data accessor

this.dataSource.sortingDataAccessor = pathDataAccessor;

Solution 5

I like @Hieu_Nguyen solutions. I'll just add that if you use lodash in you project as I do then the solution translates to this:

import * as _ from 'lodash';

this.dataSource.sortingDataAccessor = _.get; 

No need to reinvent the deep property access.

Share:
51,675

Related videos on Youtube

Roman
Author by

Roman

I study Business Informatics in fulltime and work as a Software Developer specialized in frontend. Next to web developing I'm interested in Data Science (Machine Learning, Deep learning).

Updated on December 03, 2021

Comments

  • Roman
    Roman over 2 years

    I have a normal Angular Material 2 DataTable with sort headers. All sort are headers work fine. Except for the one with an object as value. These doesn't sort at all.

    For example:

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

    note the element.project.name

    Here's the displayColumn config:

     displayedColumns = ['project.name', 'position', 'name', 'test', 'symbol'];
    

    Changing 'project.name' to 'project' doesn't work nor "project['name']"

    What am I missing? Is this even possible?

    Here's a Stackblitz: Angular Material2 DataTable sort objects

    Edit: Thanks for all your answers. I've already got it working with dynamic data. So I don't have to add a switch statement for every new nested property.

    Here's my solution: (Creating a new DataSource which extends MatTableDataSource is not necessary)

    export class NestedObjectsDataSource extends MatTableDataSource<MyObjectType> {
    
      sortingDataAccessor: ((data: WorkingHours, sortHeaderId: string) => string | number) =
        (data: WorkingHours, sortHeaderId: string): string | number => {
          let value = null;
          if (sortHeaderId.indexOf('.') !== -1) {
            const ids = sortHeaderId.split('.');
            value = data[ids[0]][ids[1]];
          } else {
            value = data[sortHeaderId];
          }
          return _isNumberValue(value) ? Number(value) : value;
        }
    
      constructor() {
        super();
      }
    }
    
    • Satya Ram
      Satya Ram over 4 years
      Could you please update the stackblitz with the fix
  • Ivar Kallejärv
    Ivar Kallejärv about 6 years
    This seems to be the best solution, small and concise, and it isn't as limited as the switch.
  • L.P.
    L.P. about 6 years
    I really really like this implementation. Cuts down on the code that has to be used/generated. I ran into a problem with the last implementation of the mat tables with this before, refreshes were causing issues. This is clean though.
  • Joey Gough
    Joey Gough over 5 years
    where did you get sort from in this.dataSource.sort = sort;
  • Mel
    Mel over 5 years
    I had to place this in ngAfterViewInit for it to work
  • TYMG
    TYMG over 5 years
    Your solution helped me the most as I realized I could return number or string. My table has both types and needed to be sorted where numbers were sorted numerically and not like strings. Using the ternary operator that checks for typing was the key to the solution.
  • JamieT
    JamieT about 5 years
    placed this next to my table declaration and it worked instantly. Saved me a ton of debugging. thanks!
  • Andy
    Andy about 5 years
    I like this solutions too. I use lodash in my project so if you use lodash, this solution translates to this: this.dataSource.sortingDataAccessor = _.get; No need to reinvent the deep property access.
  • Quentin Klein
    Quentin Klein about 5 years
    Need to be done each time the MatTableDataSource is changed (seems logic cause it encapsulate the sortingDataAccessor but anyway). Thank you !
  • Rin and Len
    Rin and Len about 5 years
    I got Cannot find name '_isNumbervalue, and assuming this is a lodash method, I can't find the method in the node module. isNumberexists. I'm not previously familiar with lodash if that's what this is. How do I use this?
  • E.Sarawut
    E.Sarawut about 5 years
    import {_isNumberValue} from "@angular/cdk/coercion";
  • aruno
    aruno almost 5 years
    @andy you should make this a separate answer. it sounds too simple to be true in a comment.. Is that all I have to do?
  • Qian Chen
    Qian Chen almost 5 years
    Shouldn't it be case 'project.name': return item['project']['name'];?
  • Mawcel
    Mawcel almost 5 years
    @JoeyGough from the ViewChild
  • Jeffrey Roosendaal
    Jeffrey Roosendaal over 4 years
    Works wonderfull, but for anyone struggling: you should name displayedColumns's as the path to the values, i.e. ['title', 'value', 'user.name']; and then use <ng-container matColumnDef="user.name"> in your template.
  • Guss
    Guss almost 4 years
    When using "strict" TypeScript, item[property] will cause errors (assuming item is some typed object). For those situations I found this answer useful: stackoverflow.com/a/55108590/53538 which is about forcing a typed object to be "indexable".
  • p4m
    p4m over 3 years
    Alternatively, you can leave the column names as-is and override the sortHeaderId independently via mat-sort-header e.g. mat-sort-header="user.name"
  • Matt Westlake
    Matt Westlake over 3 years
    1000% should be the accepted solution. This was the only solution that didn't throw typeErrors for me.
  • Sandun Sameera
    Sandun Sameera about 3 years
    You are a genius. saved me !!
  • Marc Roussel
    Marc Roussel over 2 years
    Just don't forget to mention that matColumnDef needs to match displayedColumns as for path.property like "Address.CompanyName" for both. This answer saved me.
  • Vega
    Vega almost 2 years
    This answer was already given: stackoverflow.com/a/49057493/5468463
  • Vega
    Vega almost 2 years
    This answer was already given: stackoverflow.com/a/49057493/5468463