Angular Material 2 DataTable Clasificación con objetos anidados

82

Tengo una tabla de datos de material angular 2 normal con encabezados de clasificación. Todo tipo de encabezados funcionan bien. Excepto el que tiene un objeto como valor. Estos no se clasifican en absoluto.

Por ejemplo:

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

nota la element.project.name

Aquí está la configuración de displayColumn:

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

Cambiar 'project.name'a 'project'no funciona ni"project['name']"

¿Qué me estoy perdiendo? ¿Es esto siquiera posible?

Aquí hay un Stackblitz: objetos de ordenación Angular Material2 DataTable

Editar: Gracias por todas tus respuestas. Ya lo tengo funcionando con datos dinámicos. Por lo tanto, no tengo que agregar una declaración de cambio para cada nueva propiedad anidada.

Aquí está mi solución: (No es necesario crear un nuevo DataSource que amplíe 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();
  }
}
romano
fuente
1
¿Podría actualizar el stackblitz con la solución
Satya Ram

Respuestas:

164

Fue difícil encontrar documentación sobre esto, pero es posible usando sortingDataAccessory una declaración de cambio. Por ejemplo:

@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
fuente
7
me salvaste. Gracias.
lubina
¿de dónde sacaste sorta partir dethis.dataSource.sort = sort;
Joey Gough
3
Tuve que colocar esto ngAfterViewInitpara que funcionara
Mel
Esto funcionó bien, simplemente colóquelo en ngAfterViewInit como se menciona en el comentario anterior.
Wael
colocó esto junto a la declaración de mi tabla y funcionó al instante. Me ahorró un montón de depuración. ¡Gracias!
JamieT
29

Puede escribir una función en el componente para obtener una propiedad profunda del objeto. Entonces úsalo dataSource.sortingDataAccessorcomo a continuación

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

Y en 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
fuente
1
Esta parece ser la mejor solución, pequeña y concisa, y no es tan limitada como el interruptor.
Ivar Kallejärv
Realmente me gusta mucho esta implementación. Reduce el código que se debe usar / generar. Me encontré con un problema con la última implementación de las tablas de mat con esto antes, las actualizaciones estaban causando problemas. Sin embargo, esto está limpio.
LP
3
También me gustan estas soluciones. Yo uso lodashen mi proyecto, así que si usa lodash, esta solución se traduce en esto: this.dataSource.sortingDataAccessor = _.get;No es necesario reinventar el acceso profundo a la propiedad.
Andy
1
@andy, deberías hacer de esta una respuesta separada. suena demasiado simple para ser verdad en un comentario. ¿Es eso todo lo que tengo que hacer?
Simon_Weaver
11

La respuesta dada se puede incluso acortar, sin necesidad de cambiar, siempre que utilice la notación de puntos para los campos.

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
fuente
5

Me gustan las soluciones de @Hieu_Nguyen. Solo agregaré que si usa lodash en su proyecto como lo hago yo, la solución se traduce a esto:

import * as _ from 'lodash';

this.dataSource.sortingDataAccessor = _.get; 

No es necesario reinventar el acceso profundo a la propiedad.

Andy
fuente
1
Funciona de maravilla, pero para cualquiera que tenga dificultades: debe nombrar displayedColumns's como la ruta a los valores, es decir, ['title', 'value', 'user.name'];y luego usar <ng-container matColumnDef="user.name">en su plantilla.
Jeffrey Roosendaal
4

Utilizo un método genérico que te permite usar un dot.seperated.path con mat-sort-headero matColumnDef. Esto falla al regresar silenciosamente sin definir si no puede encontrar la propiedad dictada por la ruta.

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

Solo necesita configurar el acceso a datos

this.dataSource.sortingDataAccessor = pathDataAccessor;
Toby Harris
fuente
1

Personalicé para múltiples niveles de objetos anidados.

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
fuente
Su solución me ayudó más cuando me di cuenta de que podía devolver un número o una cadena. Mi tabla tiene ambos tipos y debe ordenarse donde los números se ordenaron numéricamente y no como cadenas. El uso del operador ternario que verifica la escritura fue la clave de la solución.
TYMG
Lo tengo Cannot find name '_isNumbervalue, y asumiendo que este es un método lodash, no puedo encontrar el método en el módulo de nodo. isNumberexiste. No estoy familiarizado previamente con lodash si eso es lo que es. ¿Cómo utilizo esto?
Rin y Len
1
importar {_isNumberValue} desde "@ angular / cdk / coercion";
E.Sarawut
1

Otra alternativa, que nadie echó aquí, aplanar la columna primero ...

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

this.dataSource = new MatTableDataSource(yourData);

¡Solo otra alternativa, pros y contras para cada uno!

Tim Harker
fuente
1

Simplemente agregue esto a su fuente de datos y podrá acceder al objeto anidado

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
fuente
1
¡Muchas gracias! esto funcionó perfecto para mí.
Paco Zevallos
0

Está intentando ordenar por elemento ['project.name']. Obviamente, el elemento no tiene tal propiedad.

Debería ser fácil crear una fuente de datos personalizada que amplíe MatTableDatasource y admita la clasificación por propiedades de objetos anidados. Consulte los ejemplos en los documentos material.angular.io sobre el uso de una fuente personalizada.

funkizer
fuente
0

Tuve el mismo problema, al probar la primera proposición tuve algunos errores, pude solucionarlo agregando "switch (propiedad)"

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

    default: return item[property];
    }
  };
kawthar
fuente
0

Utilice MatTableDataSource Compruebe la solución completa del problema MatSort

en 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
fuente