Alex A.
Alex A.

Reputation: 2603

MatSort does not work with Angular Material Table when the dataSource is observable

I am trying to make one of my data tables to use a default sorting capabilities. I am using angular material table an MatSoftModule for it.

Now, I have pretty simple data loading from the API. In my controller, I am calling a service function that returns Observable:

export class DomainComponent implements OnInit {

  domains$: Observable<Domain[]>;

  columnsToDisplay = ['id', 'title'];

  constructor(private domainService: DomainService) { }

  ngOnInit() {
    this.loadDomains();
  }

  private loadDomains() {
    this.domains$ = this.domainService.getDomains();
  }

}

And in the view I am simply using that observable as dataSource:

<table mat-table [dataSource]="domains$" matSort matSortActive="id" matSortDirection="asc" class="mat-elevation-z4">

    <!-- Id Column -->
    <ng-container matColumnDef="id">
      <th mat-header-cell *matHeaderCellDef mat-sort-header> No. </th>
      <td mat-cell *matCellDef="let element" class="td-id"> {{element.id}} </td>
    </ng-container>

    <!-- Title Column -->
    <ng-container matColumnDef="title">
      <th mat-header-cell *matHeaderCellDef mat-sort-header> Title </th>
      <td mat-cell *matCellDef="let element" class="td-title"> {{element.title}} </td>
    </ng-container>

    <tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
    <tr mat-row *matRowDef="let row; columns: columnsToDisplay"></tr>
  </table>

The table itself works perfectly fine, and in this case, the headers are even shown as sortable (and even clickable). However, the sorting does not actually change.

From the documentation: https://material.angular.io/components/table/overview#sorting

It looks like I need to listen to "bind" sort function of dataSource to matSort. But, the documentation is talking about simple array of objects.

What is the simplest way to make the sorting to work without some craziness of custom DataSource implementation as described here: https://blog.angular-university.io/angular-material-data-table/ ?

Upvotes: 3

Views: 3623

Answers (1)

Martin Parenteau
Martin Parenteau

Reputation: 73751

You can sort the Observable data with the map operator. In ngOnInit, you pass the initial sort order as a parameter to loadDomains:

import { map } from "rxjs/operators";
import { Sort } from "@angular/material";

ngOnInit() {
  this.loadDomains({active: "id", direction: "asc"});
}

loadDomains(sort: Sort) {
  this.domains$ = this.domainService.getDomains().pipe(
    map((domains: Array<Domain>) => domains.sort((d1, d2) => this.sortDomains(d1, d2, sort)))
  );
}

sortDomains(d1: Domain, d2: Domain, sort: Sort) {
  const one = sort.direction === "asc" ? 1 : (sort.direction === "desc" ? -1 : 0);
  if (d1[sort.active] > d2[sort.active]) {
    return one;
  } else if (d1[sort.active] < d2[sort.active]) {
    return -one;
  } else {
    return 0;
  }
}

In the template, you call loadDomains with the new sort order in the matSortChange event handler:

<table mat-table 
  class="mat-elevation-z4" 
  [dataSource]="domains$"
  matSort 
  matSortActive="id" 
  matSortDirection="asc" 
  (matSortChange)="loadDomains($event)">

You can see a demo in this stackblitz, based on the original stackblitz provided by @AlexHaslam.

Upvotes: 3

Related Questions