Angular Material 2 DataTable Sortowanie z zagnieżdżonymi obiektami

83

Mam normalną tabelę danych Angular Material 2 DataTable z nagłówkami sortowania. Wszystkie rodzaje nagłówków działają dobrze. Z wyjątkiem tego, który ma obiekt jako wartość. Te w ogóle się nie sortują.

Na przykład:

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

zanotuj element.project.name

Oto konfiguracja displayColumn:

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

Zmiana 'project.name'na 'project'nie działa ani"project['name']"

czego mi brakuje? Czy to w ogóle możliwe?

Oto obiekty sortowania Stackblitz: Angular Material2 DataTable

Edycja: dzięki za wszystkie odpowiedzi. Już mam to do pracy z danymi dynamicznymi. Nie muszę więc dodawać instrukcji switch dla każdej nowej zagnieżdżonej właściwości.

Oto moje rozwiązanie: (Tworzenie nowego źródła danych, które rozszerza MatTableDataSource, nie jest konieczne)

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();
  }
}
rzymski
źródło
1
Czy mógłbyś zaktualizować stackblitz z poprawką
Satya Ram

Odpowiedzi:

168

Trudno było znaleźć dokumentację na ten temat, ale jest to możliwe za pomocą sortingDataAccessorinstrukcji i switch. Na przykład:

@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;
}
Steve Sanders
źródło
7
uratowałeś mnie. Dziękuję Ci.
labraks morski
1
skąd masz sortod wthis.dataSource.sort = sort;
Joey Gough
3
Musiałem to umieścić ngAfterViewInit, aby zadziałało
Mel
To działało dobrze, po prostu umieściłem go w ngAfterViewInit, jak wspomniano w powyższym komentarzu.
Wael
umieściłem to obok mojej deklaracji tabeli i zadziałało natychmiast. Zaoszczędziło mi mnóstwo debugowania. dzięki!
JamieT
31

Możesz napisać funkcję w komponencie, aby uzyskać głęboką właściwość z obiektu. Następnie użyj go dataSource.sortingDataAccessorjak poniżej

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

Oraz w 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>
Hieu Nguyen
źródło
1
Wydaje się, że jest to najlepsze rozwiązanie, małe i zwięzłe, i nie jest tak ograniczone jak przełącznik.
Ivar Kallejärv
Bardzo mi się podoba ta realizacja. Ogranicza kod, który ma zostać użyty / wygenerowany. Napotkałem problem z ostatnią implementacją tabel mat z tym wcześniej, odświeżenia powodowały problemy. To jest jednak czyste.
LP
3
Te rozwiązania też mi się podobają. Używam lodashw moim projekcie, więc jeśli używasz lodash, to rozwiązanie przekłada się na to: this.dataSource.sortingDataAccessor = _.get;nie ma potrzeby ponownego wymyślania głębokiego dostępu do nieruchomości.
Andy
2
@andy powinieneś zrobić z tego osobną odpowiedź. brzmi to zbyt prosto, aby mogło być prawdziwe w komentarzu. Czy to wszystko, co muszę zrobić?
Simon_Weaver,
11

Podaną odpowiedź można nawet skrócić, bez przełączania, o ile używasz notacji kropkowej dla pól.

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;
}
Erik Schaareman
źródło
5

Lubię rozwiązania @Hieu_Nguyen. Dodam tylko, że jeśli użyjesz lodash w swoim projekcie tak jak ja, to rozwiązanie przekłada się na to:

import * as _ from 'lodash';

this.dataSource.sortingDataAccessor = _.get; 

Nie ma potrzeby ponownego odkrywania głębokiego dostępu do nieruchomości.

Andy
źródło
1
Działa wspaniale, ale dla każdego, kto ma problemy: należy nazwać displayedColumnsścieżkę do wartości, np. ['title', 'value', 'user.name'];A następnie użyć jej <ng-container matColumnDef="user.name">w szablonie.
Jeffrey Roosendaal
Alternatywnie możesz pozostawić nazwy kolumn bez zmian i niezależnie nadpisać sortHeaderId przez mat-sort-headernp.mat-sort-header="user.name"
p4m
4

Używam ogólnej metody, która pozwala na użycie kropki rozdzielonej ścieżki z mat-sort-headerlub matColumnDef. To kończy się niepowodzeniem i po cichu zwraca undefined, jeśli nie może znaleźć właściwości podyktowanej ścieżką.

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

Wystarczy ustawić akcesor danych

this.dataSource.sortingDataAccessor = pathDataAccessor;
Toby Harris
źródło
1

Dostosowałem się do wielu zagnieżdżonych poziomów obiektów.

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;
  };
E.Sarawut
źródło
Twoje rozwiązanie pomogło mi najbardziej, ponieważ zdałem sobie sprawę, że mogę zwrócić liczbę lub ciąg. Moja tabela ma oba typy i musiała zostać posortowana tam, gdzie liczby zostały posortowane numerycznie, a nie jak ciągi. Kluczem do rozwiązania było użycie operatora trójargumentowego, który sprawdza wpisywanie.
TYMG
Mam Cannot find name '_isNumbervaluei zakładając, że jest to metoda lodash, nie mogę znaleźć metody w module węzła. isNumberistnieje. Nie znam wcześniej lodash, jeśli to jest to. Jak tego używam?
Rin i Len
1
import {_isNumberValue} z "@ angular / cdk / coercion";
E.Sarawut
1

Inna alternatywa, której nikt tu nie wyrzucił, najpierw spłaszcz kolumnę ...

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

this.dataSource = new MatTableDataSource(yourData);

Po prostu kolejna alternatywa, plusy i minusy dla każdego!

Tim Harker
źródło
1

Po prostu dodaj to do źródła danych, a będziesz mieć dostęp do zagnieżdżonego obiektu

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];
};
Chan Jing Hong
źródło
0

Próbuje sortować według elementu [„nazwa.projektu”]. Oczywiście element nie ma takiej właściwości.

Utworzenie niestandardowego źródła danych, które rozszerza MatTableDatasource i obsługuje sortowanie według właściwości zagnieżdżonych obiektów, powinno być łatwe. Zapoznaj się z przykładami w material.angular.io dokumentach dotyczących korzystania z niestandardowego źródła.

funkizer
źródło
0

Miałem ten sam problem, testując pierwszą propozycję miałem kilka błędów, mogłem to naprawić dodając „przełącznik (właściwość)”

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

    default: return item[property];
    }
  };
kawthar
źródło
0

Użyj MatTableDataSource Sprawdź pełne rozwiązanie problemu MatSort

w 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;
}
Priti jha
źródło
0

Jeśli chcesz mieć tabelę materiałów kątowych z kilkoma rozszerzonymi funkcjami, takimi jak sortowanie obiektów zagnieżdżonych, zajrzyj na https://github.com/mikelgo/ngx-mat-table-extensions/blob/master/libs/ngx-mat -table / README.md .

Stworzyłem tę bibliotekę, ponieważ brakowało mi niektórych funkcji stołu matowego po wyjęciu z pudełka.

Zaawansowane sortowanie jest podobne do sugerowanej odpowiedzi @Hieu Nguyen, ale zostało nieco rozszerzone, aby mieć również prawidłowe sortowanie według wielkich i małych liter.

Mikelgo
źródło