Arun Selva Kumar
Arun Selva Kumar

Reputation: 2732

How to enable sorting in Angular Material Data Table, when the datasource is Observable<T>

I have a following table in my component,

<table [dataSource]="(searchResults$ | async)?.accounts" mat-table matSort multiTemplateDataRows>

          <ng-container matColumnDef="Code">
            <th *matHeaderCellDef mat-header-cell mat-sort-header> Account Code</th>
            <td *matCellDef="let account" mat-cell> {{account.code}} </td>
          </ng-container>

</table>

I have kept mat-sort directive in both the column definition and at table level.
All the examples provided in https://material.angular.io/components/table/examples - lists down dataSource when set from TS file not as observable stream.

Upvotes: 9

Views: 7109

Answers (4)

Crash1hd
Crash1hd

Reputation: 673

I do realize this is a very old question (but just incase someone else needs it in the future) Like I did. This can totally be done. Start with the html as in the question above. What has an async observable response.

<table [dataSource]="(searchResults$ | async)?.accounts" mat-table matSort multiTemplateDataRows>

          <ng-container matColumnDef="Code">
            <th *matHeaderCellDef mat-header-cell mat-sort-header> Account Code</th>
            <td *matCellDef="let account" mat-cell> {{account.code}} </td>
          </ng-container>

</table>

replace it like so

<div *ngIf="{dataSource: (dataSource$ | async) } as observable">
  <table
    mat-table
    [dataSource]="observable.dataSource"
    class="mat-elevation-z8"
    matSort
    (matSortChange)="sortData(observable.dataSource)"
  >

note either have the matSortChange call the sortData function or have that sortData function be called inside where the observable is created in the ts file (for simplicty the example shows the first way)

Then you make sure that the searchResults$ is an observable that contains a MatTableDataSource

searchResults$: Observable<any>;

searchResults$ = of(new MatTableDataSource(dataArray));

Of only used here as an example (as its not a great practice to use of in general.

Then either in the pipe if you have one or in the html add (matSortChange)="sortData(dataSource)"

  private sortData(data: any) {
    data.sort = this.sort;
  }

and remember to add @ViewChild(MatSort) sort: MatSort; inside the angular class

The key is that the data in dataSource has to be of MatTableDataSource for sort etc to work.

working example https://stackblitz.com/edit/angular-6fomv1-52nyue?file=app/table-basic-example.html

I should note you can also just call a subscribe on your observable in the ts file and do it all in there instead.

Upvotes: 3

Luis Fernando
Luis Fernando

Reputation: 1294

In case you need a loading(async) then after loading yo need to render the table and set the sort async. try this

component.html

 <loading-component *ngIf="isLoading$ | async"></loading-component> <--WHILE Loading

<div *ngIf="!(isLoading$ | async)"> <-- AFTER Loading
    <ng-container *ngIf="dataSource.data.length >= 1; else noData">  <--Check if there's data
        <table mat-table [dataSource]="dataSource" matSort>
            ...
        </table>
    </ng-container>
    <ng-template #noData>  <- In case there is not data (to not show the table/table-header)
        ...
    </ng-template>
</div>

component.ts

import { MatTableDataSource } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { Observable } from 'rxjs/Observable';

export class Component {
  dataSource = new MatTableDataSource<any>();
  isLoading$: Observable<boolean>;  
  private _sort: MatSort;
  
  @ViewChild(MatSort) set matSort(ms: MatSort) {
    this._sort = ms;
    this.dataSource.sort = this._sort;
  }
.. the rest of the component logic ...

With the SET Sort you can fetch on the fly(async) data to table and then sort it.

Keep coding \m/

Upvotes: -1

lizzmo
lizzmo

Reputation: 199

Was just banging my head against the wall about this one for a while so putting this here to hopefully save someone some time. What worked for me:

component.ts:

@Input() dataSource;

@ViewChild(MatSort, {static: true}) sort: MatSort;

ngAfterViewInit() {
  this.dataSource.subscribe(data => {
    this.dataSource = new MatTableDataSource(data);
    this.dataSource.sort = this.sort;
  });
}

component.html:

<app-table [dataSource]="yourService.getData()"></app-table>

Make sure you don't use the async pipe when passing the Observable to dataSource.

I'm new to Angular, TS and RxJS (usually work with React) so if anyone sees issues with this approach let me know!

Upvotes: 0

Roberto Zvjerković
Roberto Zvjerković

Reputation: 10157

The same way. You will just sort the elements inside the Observable.

(matSortChange)="sortData($event)"

And then sort it:

sortData(sort: Sort) {
    this.searchResults$ = this.searchResults$.pipe(map(results => {
        // sort the results like in examples
    }));
  }

Upvotes: 1

Related Questions