Reputation: 3149
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();
}
}
Upvotes: 131
Views: 69730
Reputation: 11
this.dataSource.data.map((res)=>{
res.name = res.project.name;
})
If you want to sort by name then you can manipulate the original array using the map
Upvotes: 0
Reputation: 9663
If your data contain inner object like below example
myData = [ {service: 'SRS',
team: {id: 'T1', name: 'Blasters'},
rank: 23,
.....
},
{ service: 'COC',
team: {id: 'T2', name: 'Strikers'},
rank: 7,
.....
},
......
]
then you can use a custom sorting function
<table mat-table [dataSource]="myData" #outersort="matSort" matSort>
<ng-container matColumnDef="service">
<th mat-header-cell *matHeaderCellDef mat-sort-header>SERVICE</th>
<td mat-cell *matCellDef="let element">
{{ element.service }}
</td>
</ng-container>
<ng-container matColumnDef="team.name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>TEAM</th>
<td mat-cell *matCellDef="let element">
{{ element.team.name }}
</td>
</ng-container>
.....
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
displayed column should represent nested object as follows
displayedColumns = ['service','team.name', 'rank']
and in ts file
@ViewChild(MatSort) MySort: MatSort;
.....
this.dataSource = new MatTableDataSource(myData);
this.dataSource.sortingDataAccessor = (item, property) => {
return eval('item.'+property);
};
this.dataSource.sort = MySort;
.....
Upvotes: 0
Reputation: 680
I find a clear solution and works for any input data.
this.dataSource.sortingDataAccessor = (obj: any, property: string) => property.split('.').reduce((o, p) => o && o[p], obj);
this.dataSource.sort = this.sort;
In the HTML file you need to set the nested object to the mat-sort-header. EX: mat-sort-header="User.Name"
<ng-container matColumnDef="UserName">
<th mat-header-cell *matHeaderCellDef mat-sort-header="User.Name">User</th>
<td mat-cell *matCellDef="let row">{{ row.User.Name}}</td>
</ng-container>
Explanation: First step: set your data source. Sec.: set the generic sorting data accelerator. Third: call the sort to the datasource.
this.dataSource = new MatTableDataSource(project.ActivityLog.items);
setTimeout(() => {
this.dataSource.sortingDataAccessor = (obj: any, property: string) => property.split('.').reduce((o, p) => o && o[p], obj);
this.dataSource.sort = this.sort;
})
}
Upvotes: 0
Reputation: 2023
My table columns were not ordering correctly, so I modified one of the answers to work with my data.
function pathDataAccessor(item: any, path: string): any {
return (item: any, path: string): any => {
return path.split(".").reduce((accumulator: any, key: string) => {
let returnValue;
if (accumulator) {
returnValue = accumulator[key];
} else {
returnValue = undefined;
}
if (typeof returnValue === "string") {
returnValue = returnValue.trim().toLocaleLowerCase();
}
return returnValue;
}, item);
};
}
Upvotes: 0
Reputation: 575
If you want to have an Angular material table with some extended features, like sorting for nested objects have a look at https://github.com/mikelgo/ngx-mat-table-extensions/blob/master/libs/ngx-mat-table/README.md .
I created this lib because I was missing some features of mat-table out of the box.
The advanced sorting is similar to @Hieu Nguyen suggested answer but a bit extended to also have proper sorting by upper and smaller case letters.
Upvotes: 1
Reputation: 2461
Just add this to your data source and you will be able to access the nested object
this.dataSource.sortingDataAccessor = (item, property) => {
// Split '.' to allow accessing property of nested object
if (property.includes('.')) {
const accessor = property.split('.');
let value: any = item;
accessor.forEach((a) => {
value = value[a];
});
return value;
}
// Access as normal
return item[property];
};
Upvotes: 2
Reputation: 143
Use MatTableDataSource Check complete MatSort issue solution
in HTML
<ng-container matColumnDef="createdDate" @bounceInLeft>
<th mat-header-cell *matHeaderCellDef mat-sort-header class="date"> Created date
</th>
<td mat-cell *matCellDef="let element" class="date"> {{element.createdDate
| date :'mediumDate'}} </td>
</ng-container>
<ng-container matColumnDef="group.name">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="type"> Group </th>
<td mat-cell *matCellDef="let element" class="type"> {{element.group.name}} </td>
</ng-container>
@ViewChild(MatSort, { static: true }) 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;
}
Upvotes: 0
Reputation: 303
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.
Upvotes: 13
Reputation: 2407
Another alternative, that no one threw out here, flatten the column first...
yourData.map((d) =>
d.flattenedName = d.project && d.project.name ?
d.project.name :
'Not Specified');
this.dataSource = new MatTableDataSource(yourData);
Just another alternative, pros and cons for each!
Upvotes: 1
Reputation: 8651
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;
}
Upvotes: 251
Reputation: 191
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;
}
Upvotes: 19
Reputation: 141
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;
Upvotes: 14
Reputation: 11
I customized for multiple nested object level.
this.dataSource.sortingDataAccessor =
(data: any, sortHeaderId: string): string | number => {
let value = null;
if (sortHeaderId.includes('.')) {
const ids = sortHeaderId.split('.');
value = data;
ids.forEach(function (x) {
value = value? value[x]: null;
});
} else {
value = data[sortHeaderId];
}
return _isNumberValue(value) ? Number(value) : value;
};
Upvotes: 1
Reputation: 451
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>
Upvotes: 45
Reputation: 119
I had the same issue, by testing the first proposition I had some errors, I could fixe it by adding "switch (property)"
this.dataSource.sortingDataAccessor =(item, property) => {
switch (property) {
case 'project.name': return item.project.name;
default: return item[property];
}
};
Upvotes: 0
Reputation: 4897
It's trying to sort by element['project.name']. Obviously element doesn't have such a property.
It should be easy to create a custom datasource that extends MatTableDatasource and supports sorting by nested object properties. Check out the examples in material.angular.io docs on using a custom source.
Upvotes: 0