10

I'm trying to sort the MatTableDataSource programmatically so that I can sort the data via the use of a button rather than by the table column header when viewing the data in a specific mobile layout. However, I'm having trouble doing so.

I've followed this post's advice on how to do it but the data sort is not being reflected. The mobile layout design for using the same data as the table:

<mat-card matSort *ngFor="let item of dataSource.filteredData">

The function I'm using to try and sort the data is:

sortDataSource(id: string, start: string){
  this.dataSource.sort.sort(<MatSortable>({id: id, start: start}));
}

which is called from tapping onto a button within a mat-menu e.g.

<mat-menu #sortMenu="matMenu" yPosition="below" [overlapTrigger]="false">
            <button mat-menu-item (click)="sortDataSource('createdDate', 'asc')"><span>Creation Date</span><mat-icon>arrow_upward</mat-icon></button>

Any help on this would be greatly appreciated!

Link to StackBlitz.

Edit: Adding where I'm assigning the data to the table's dataSource which comes from an Observable array:

this.data$.subscribe(data => {
      this.dataSource.data = data;
      this.dataSource.paginator = this.paginator;
      this.dataSource.sort = this.sort;
}

Edit 2: Removed error image as fixed by add "matSort" to the the mat-card using *ngFor for creating the mobile layout version of the mat-table.

Edric
  • 24,639
  • 13
  • 81
  • 91
soupjake
  • 3,293
  • 3
  • 17
  • 32
  • Where do you define `this.dataSource`? You are not actually sorting anything but rather setting a sorting id and direction. – Julien Ambos Mar 04 '19 at 11:35
  • Within the ngOnInit. I'm able to sort the data fine when using the mat-table's column headers but I'm trying to sort it with the use of a button instead by using the MatTableDataSource's methods but I can't find much documentation on it. – soupjake Mar 04 '19 at 11:41
  • The mat-sort directive is connected to the table and therefore sorts the data, but manually doing it is different. Please post your dataSource code – Julien Ambos Mar 04 '19 at 11:59
  • Added in edit as requested. – soupjake Mar 04 '19 at 12:05
  • Managed to fix the error message as said by the edit but the sort is still not being reflected by the data. – soupjake Mar 04 '19 at 12:18

5 Answers5

5

UPDATE

A bug was already reported to the Material team over at Github.com. It seems that this is currently not possible. Please use a manual approach or implement your own sorting header.


Try manually sorting the data of the datasource:

sortDataSource(id: string, start: string) {
    this.dataSource.sort.sort(<MatSortable>({ id: id, start: start }));
    this.dataSource.data.sort((a: any, b: any) => {
        if (a.createdDate < b.createdDate) {
            return -1;
        } else if (a.createdDate > b.createdDate) {
            return 1;
        } else {
            return 0;
        }
    });
}

You can easily make this more generic using the id and start parameters of your function.

Julien Ambos
  • 2,010
  • 16
  • 29
  • 2
    I don't really want to be manually sorting the data though. I want a way of accessing the MatTableDataSource's inbuilt method of sorting since I'm already defining how to sort the data within the `this.dataSource.sortingDataAccessor` method so that I can emulate the way of pressing onto a column header via the use of a button by passing the `id` and `start` props through. Maybe it's just not possible to do that? – soupjake Mar 04 '19 at 12:53
  • Okay added one. The sort button above the table opens up a mat-menu which then uses the sort function when selecting an item. – soupjake Mar 04 '19 at 13:44
  • Aaaa okay that explains it then! Yeah will just have to do it manually at the moment then. Thanks! – soupjake Mar 04 '19 at 14:26
  • A shorter way would be this.dataSource.data.sort((a: any, b: any) => { return a.createdDate - b.createdDate ; } -> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort – AndyNope Oct 01 '21 at 13:51
  • 2
    Or even shorter with asc or desc: this.dataSource.data.sort( (a, b) => { return start === 'asc' ? a[id] - b[id] : (start === 'desc' ? b[id] - a[id] : a[id] - b[id]); // Last is default } ); – AndyNope Oct 01 '21 at 14:05
4

https://stackblitz.com/edit/angular-mat-sort-default-ytu6ch

check this out!

instead of manually sorting with the array.sort we can make use of the @ViewChild(MatSort) sort!: MatSort; and import { MatSort, Sort } from '@angular/material/sort';

to sort based on condition.

1

When you reassign dataSource.sort with matSort, the template gets notified on the change and represent the changes.

component.ts

@ViewChild(MatSort, { static: false }) matSort: MatSort;

  orderData(id: string, start?: 'asc' | 'desc') {
    const matSort = this.dataSource.sort;
    const toState = 'active';
    const disableClear = false;

    matSort.sort({ id: null, start, disableClear });
    matSort.sort({ id, start, disableClear });

    this.dataSource.sort = this.matSort;
  }

component.html

<button mat-raised-button
   (click)="orderData('nameOfColumn', 'asc')">ASC</button>
<button mat-raised-button
   (click)="orderData('nameOfColumn', 'desc')">DESC</button>
Shalem
  • 1,446
  • 2
  • 22
  • 47
Jay Ahn
  • 73
  • 5
1

Might be the shortest sort manually function for Material Table

Here is my short solution combined with JavaScript:

sortDataSource(idVal: string, startVal?: string): void {
    this.dataSource.data.sort(
        (a, b) => {
            return startVal === 'asc' ? a[idVal] - b[idVal] : (startVal === 'desc' ? b[idVal] - a[idVal] : a[idVal] - b[idVal]);
        }
    );
}
AndyNope
  • 427
  • 6
  • 12
0

For this to work with nested objects inside your datasource just place following in your datasource initialization routine:

...

this.dataSource.sort = new MatSort()

this.dataSource.sortingDataAccessor = _.get

Where _.get is a lodash function (https://docs-lodash.com/v4/get/)

My sorting function that is called whenever my sorting select control changes is as follows:

private sortDataSource(sortItem: SortItem) {
        console.log('Sort:: ', sortItem)

        this.dataSource.sort.sort(<MatSortable>{
            id: 'ad.createdAt',
            start:
                sortItem.sortOrder === SortOrder.ASCENDING ? 'asc' : 'desc',
            disableClear: true,
        })
        this.dataSource.data.sort(
            (a: any, b: any) => {
                
                if (
                    a['ad']['createdAt'] <
                    b['ad']['createdAt']
                ) {
                    if (sortItem.sortOrder === -1) {
                        return -1
                    } else {
                        return 1
                    }
                } else if (
                    a['ad']['createdAt'] >
                    b['ad']['createdAt']
                ) {
                    if (sortItem.sortOrder === -1) {
                        return 1
                    } else {
                        return -1
                    }
                } else {
                    return 0
                }
            }
        )

    }

For the sake of completeness here is my SortItem interface thats used to specify sort items:

export interface SortItem {
    title: string
    adProperty: string
    sortOrder: SortOrder
}

export enum SortOrder {
    DESCENDING = -1, // 'descending',
    ASCENDING = 1, // 'ascending'
}
Torsten Barthel
  • 3,059
  • 1
  • 26
  • 22