Angular mat-table: multiple rows per record with sorting option

I want to create a table when each records has two table rows. I achived this by splitting the data to separate rows. The problem is sorting – I want to sort records, not a table rows. Funny thing is that it’s almost works as expected in my stackblitz example, the only problem is when DESC is set as sorting order.

ASC:

enter image description here

DESC:

enter image description here

As you can see for desc order the record should have switch its places.

Here is an example that I prepared: https://stackblitz.com/edit/material2-rc0-6rzgwh?file=app/app.component.ts

In my application when I have to use it I have my custom sortingDataAccessor because some of <td> are not just strings but more complex elements:

this.dataSource.sortingDataAccessor = (item, property) => {
      if (item[property]) {
        return this.parseValue(item[property]);
      } else {
        for (const column of this.columns) {
          if (column.fieldPath && column.id === property) {
            return this.parseValue(getNestedValue(item, column.fieldPath));
          }
        }
      }
    };

So in my case each of table rows are included to sort so "the descriptions" rows are next to each other, and then the other rows are next to each other. Can someone help me with that?

11 thoughts on “Angular mat-table: multiple rows per record with sorting option”

  1. I have been looking at what you have done here and I think that it doesn’t work for you because of the setData function. The fact that you push a ‘description’ row in your dataSet is confusing the Sorting function because it wants to sort it as well but it can’t find the selected column key in the ‘description’ objects.

    Also, the fact that you have 2 different mat-row items is confusing.

    I would like to suggest a different solution that can resolve some of your issues in a cleaner way.

    import { Component, AfterViewInit, ViewChild } from "@angular/core";
    import { DataSource } from "@angular/cdk/table";
    import { Observable } from "rxjs/Observable";
    import { BehaviorSubject } from "rxjs/BehaviorSubject";
    import "rxjs/Rx";
    import { MatSort, MatTableDataSource } from "@angular/material";
    
    @Component({
      selector: "my-app",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.scss"]
    })
    export class AppComponent implements AfterViewInit {
      @ViewChild(MatSort) sort: MatSort;
      dataSource: MatTableDataSource<any>;
    
      isDescriptionRow = (index, item) => item.description;
      displayedColumns = ['id', 'data'];
      constructor() {
        // this.dataSource = new MatTableDataSource<any>(this.setData(DATA));
        this.dataSource = new MatTableDataSource<any>(DATA);
      }
    
      ngAfterViewInit() {
        if (this.dataSource) {
          this.dataSource.sort = this.sort;
    
          this.dataSource.sortingDataAccessor = (item, property) => {
            console.log(item);
            return item[property];
          };
        }
      }
    
      // setData(vals: any[]) {
      //   const splitVals = [];
    
      //   vals.forEach(v => {
      //     splitVals.push({ data: v.data, id: v.id });
      //     splitVals.push({ description: v.description });
      //   });
    
      //   return splitVals;
      // }
    }
    
    const DATA = [
      { data: "ABC", id: 1, description: "Blah blah info about ABC" },
      { data: "DEF", id: 2, description: "Blah blah info about DEF" },
      { data: "GHI", id: 3, description: "Blah blah info about GHI" },
      { data: "JKL", id: 4, description: "Blah blah info about JKL" },
      { data: "MNO", id: 5, description: "Blah blah info about MNO" }
    ];
    

    You will notice here that I just left data as it was without manipulations.

    <app-header></app-header>
    
    <div style="padding: 32px">
    
      <mat-table [dataSource]="dataSource" matSort>
    
        <ng-container matColumnDef="id">
          <mat-header-cell *matHeaderCellDef mat-sort-header> ID </mat-header-cell>
          <mat-cell *matCellDef="let item">
            <div>{{item.id}}</div>
            <div>{{item.description}}</div>
          </mat-cell>
        </ng-container>
        
        <ng-container matColumnDef="data">
          <mat-header-cell *matHeaderCellDef mat-sort-header> Data </mat-header-cell>
          <mat-cell *matCellDef="let item">
             {{item.data}}
          </mat-cell>
        </ng-container>
    
        <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
        <mat-row *matRowDef="let row; columns: displayedColumns;" class="light-border">
      
        </mat-row>
        <!-- <mat-row *matRowDef="let row; columns: ['description']; when: isDescriptionRow">
        </mat-row> -->
    
      </mat-table>
    
    </div>
    

    Now you will notice that sorting works just fine.

    Some adjustments with css can do the trick for you with placing the elements in the way you would like.

    I have left the mat-row in comment, I suggest you take this code and uncomment it to see what happens, it overrides the previous mat-row.

    I know this is not exactly what you were looking for, but maybe it can take you one step forward to the desired result you are looking for.

    Maybe you will be able to achieve what you are looking for after updating dependencies.
    look at this example:
    example of multiline rows
    just remove the expandable logic and you will see multiline rows

    Reply

Leave a Comment