Angular Material 2 DataTable Сортировка с вложенными объектами


83

У меня есть обычный Angular Material 2 DataTable с заголовками сортировки. Все сортировки заголовков работают нормально. За исключением того, у которого в качестве значения есть объект. Это совсем не сортировка.

Например:

 <!-- 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: Angular Material2 DataTable

Изменить: спасибо за все ваши ответы. У меня уже есть работа с динамическими данными. Поэтому мне не нужно добавлять оператор switch для каждого нового вложенного свойства.

Вот мое решение: (Создание нового источника данных, расширяющего MatTableDataSource, не требуется)

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

1
Не могли бы вы обновить stackblitz исправлением
Сатья Рам

Ответы:


168

Найти документацию по этому поводу было сложно, но это возможно с помощью sortingDataAccessorоператора и switch. Например:

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

7
вы спасли меня. Спасибо.
seabass

1
где вы получите sortот вthis.dataSource.sort = sort;
Joey Гоф

3
Я должен был поместить это, ngAfterViewInitчтобы это сработало
Мел

Это сработало хорошо, просто поместил его в ngAfterViewInit, как указано в комментарии выше.
Ваэль

поместил это рядом с моим объявлением таблицы, и он сработал мгновенно. Сэкономил мне тонну отладки. благодаря!
JamieT

31

Вы можете написать функцию в компоненте, чтобы получить глубокое свойство объекта. Затем используйте его, dataSource.sortingDataAccessorкак показано ниже

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

И в 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>

1
Кажется, это лучшее решение, маленькое и лаконичное, и оно не так ограничено, как переключатель.
Ivar Kallejärv

Мне очень нравится эта реализация. Сокращает количество кода, который необходимо использовать / сгенерировать. У меня возникла проблема с последней реализацией таблиц mat с этим раньше, обновления вызывали проблемы. Хотя это чисто.
LP

3
Мне тоже нравятся эти решения. Я использую lodashв своем проекте, поэтому, если вы используете lodash, это решение означает следующее: this.dataSource.sortingDataAccessor = _.get;нет необходимости заново изобретать доступ к глубоким свойствам.
Энди

2
@andy, вы должны сделать это отдельным ответом. в комментарии это звучит слишком просто, чтобы быть правдой .. Это все, что мне нужно сделать?
Simon_Weaver

11

Ответ в том виде, в каком он дан, можно даже сократить, переключение не требуется, если вы используете точечную нотацию для полей.

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

5

Мне нравятся решения @Hieu_Nguyen. Я просто добавлю, что если вы используете lodash в своем проекте, как это делаю я, решение преобразуется в следующее:

import * as _ from 'lodash';

this.dataSource.sortingDataAccessor = _.get; 

Нет необходимости заново изобретать глубокий доступ к собственности.


1
Прекрасно работает, но для всех, кто испытывает затруднения: вы должны указать displayedColumns's как путь к значениям, т.е. ['title', 'value', 'user.name'];а затем использовать <ng-container matColumnDef="user.name">в своем шаблоне.
Джеффри Розендал,

В качестве альтернативы вы можете оставить имена столбцов как есть и переопределить sortHeaderId независимо, mat-sort-headerнапример,mat-sort-header="user.name"
p4m

4

Я использую общий метод, который позволяет использовать dot.seperated.path с mat-sort-headerили matColumnDef. Это не может молча вернуть undefined, если не может найти свойство, продиктованное путем.

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

Вам просто нужно установить аксессор данных

this.dataSource.sortingDataAccessor = pathDataAccessor;

1

Я настроил для уровня нескольких вложенных объектов.

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

Ваше решение помогло мне больше всего, поскольку я понял, что могу вернуть число или строку. Моя таблица имеет оба типа и должна быть отсортирована там, где числа отсортированы численно, а не как строки. Ключом к решению было использование тернарного оператора, проверяющего ввод текста.
TYMG

Я получил Cannot find name '_isNumbervalue, и, предполагая, что это метод lodash, я не могу найти метод в модуле узла. isNumberсуществует. Я раньше не знаком с lodash, если это то, что это такое. Как мне это использовать?
Рин и Лен

1
импортировать {_isNumberValue} из "@ angular / cdk / coercion";
Э. Саравут

1

Еще одна альтернатива, которую здесь никто не выбросил, сначала сплющите колонну ...

yourData.map((d) => 
   d.flattenedName = d.project && d.project.name ? 
                     d.project.name : 
                     'Not Specified');

this.dataSource = new MatTableDataSource(yourData);

Еще одна альтернатива, плюсы и минусы для каждого!


1

Просто добавьте это в свой источник данных, и вы сможете получить доступ к вложенному объекту

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

0

Он пытается отсортировать по элементу ['project.name']. Очевидно, что у элемента нет такого свойства.

Должно быть легко создать пользовательский источник данных, который расширяет MatTableDatasource и поддерживает сортировку по свойствам вложенных объектов. Ознакомьтесь с примерами в документации material.angular.io по использованию настраиваемого источника.


0

У меня была та же проблема, при тестировании первого предложения у меня были некоторые ошибки, я мог исправить это, добавив «переключатель (свойство)»

this.dataSource.sortingDataAccessor =(item, property) => {
    switch (property) {
    case 'project.name': return item.project.name;

    default: return item[property];
    }
  };

0

Используйте MatTableDataSource. Проверьте полное решение проблемы с MatSort.

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

0

Если вы хотите иметь таблицу материалов Angular с некоторыми расширенными функциями, такими как сортировка вложенных объектов, посмотрите https://github.com/mikelgo/ngx-mat-table-extensions/blob/master/libs/ngx-mat -таблица / README.md .

Я создал эту библиотеку, потому что мне не хватало некоторых функций mat-table из коробки.

Расширенная сортировка аналогична предложенному ответу @Hieu Nguyen, но немного расширена, чтобы также иметь правильную сортировку по заглавным и меньшим буквам.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.