Angular Material 2 DataTable Sorting with nested objects
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.
Related videos on Youtube
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, 2021Comments
-
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 over 4 yearsCould you please update the stackblitz with the fix
-
-
Ivar Kallejärv about 6 yearsThis seems to be the best solution, small and concise, and it isn't as limited as the switch.
-
L.P. about 6 yearsI 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 over 5 yearswhere did you get
sort
from inthis.dataSource.sort = sort;
-
Mel over 5 yearsI had to place this in
ngAfterViewInit
for it to work -
TYMG over 5 yearsYour 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 about 5 yearsplaced this next to my table declaration and it worked instantly. Saved me a ton of debugging. thanks!
-
Andy about 5 yearsI like this solutions too. I use
lodash
in my project so if you uselodash
, this solution translates to this:this.dataSource.sortingDataAccessor = _.get;
No need to reinvent the deep property access. -
Quentin Klein about 5 yearsNeed to be done each time the
MatTableDataSource
is changed (seems logic cause it encapsulate thesortingDataAccessor
but anyway). Thank you ! -
Rin and Len about 5 yearsI got
Cannot find name '_isNumbervalue
, and assuming this is a lodash method, I can't find the method in the node module.isNumber
exists. I'm not previously familiar with lodash if that's what this is. How do I use this? -
E.Sarawut about 5 yearsimport {_isNumberValue} from "@angular/cdk/coercion";
-
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 almost 5 yearsShouldn't it be
case 'project.name': return item['project']['name'];
? -
Mawcel almost 5 years@JoeyGough from the ViewChild
-
Jeffrey Roosendaal over 4 yearsWorks 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 almost 4 yearsWhen using "strict" TypeScript,
item[property]
will cause errors (assumingitem
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 over 3 yearsAlternatively, 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 over 3 years1000% should be the accepted solution. This was the only solution that didn't throw typeErrors for me.
-
Sandun Sameera about 3 yearsYou are a genius. saved me !!
-
Marc Roussel over 2 yearsJust 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 almost 2 yearsThis answer was already given: stackoverflow.com/a/49057493/5468463
-
Vega almost 2 yearsThis answer was already given: stackoverflow.com/a/49057493/5468463