使用嵌套对象进行角度材质2数据表排序

我有一个正常的角材料2数据表与排序标题。 所有排序都是头部工作良好。除了一个对象作为值。 这些根本不能分类。

例如:

 <!-- 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>

注意 element.project.name

下面是 displayColumn 配置:

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

'project.name'改为 'project'不起作用,"project['name']"也不起作用

我错过了什么,这有可能吗?

这里有一个 Stackblitz: 角材质2数据表排序对象

编辑: 谢谢你的回答。 我已经让它处理动态数据了。因此,我不必为每个新的嵌套属性添加 switch 语句。

以下是我的解决方案: (不需要创建扩展 MatTableDataSource 的新 DataSource)

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();
}
}
57588 次浏览

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.

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;
}

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];
}
};

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>

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;
};

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;

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;
}

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!

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.

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;
}

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];
};

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.

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);
};
}


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;


})
}