6

So I have a working Angular Material Data Table in my Angular 5 app, but when I tried adding the sorting functionality (based on the offical docs here: https://material.angular.io/components/table/overview#sorting and an example here: https://stackblitz.com/angular/dnbermjydavk?file=app%2Ftable-overview-example.html ) I can't get it to work. It does seem to add the sorting functionality/arrow, I can click it, but nothing happens.

Here's my HTML:

<div class="container">
  <mat-table #table class="dataTable" *ngIf="showDataForm;else loadingTemplate" [dataSource]="dataSource" matSort>
    <ng-container matColumnDef="id">
      <mat-header-cell *matHeaderCellDef mat-sort-header>ID</mat-header-cell>
      <mat-cell *matCellDef="let item">{{item.id}}</mat-cell>
    </ng-container>
    <ng-container matColumnDef="titel">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Titel</mat-header-cell>
      <mat-cell *matCellDef="let item">{{item.titel}}</mat-cell>
    </ng-container>
    <ng-container matColumnDef="EADDraftingStage">
      <mat-header-cell *matHeaderCellDef mat-sort-header>EADDraftingStage</mat-header-cell>
      <mat-cell *matCellDef="let item">{{item.EADDraftingStage}}</mat-cell>
    </ng-container>

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

  <mat-paginator [pageSize]="10" [pageSizeOptions]="[5, 10, 25]" showFirstLastButtons></mat-paginator>
</div>

<ng-template #loadingTemplate>
  <div>
      <p>Please wait, the data is loading...</p>
      <img src="../../assets/giphy.gif">
  </div>
</ng-template>

<button mat-raised-button class="submitButton" color="accent" (click)="logout()">Logout and remove cookie</button>  

Here's my TS:

import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { CookieService } from 'ngx-cookie-service';
import { LoginService } from '../Services/login.service';
import { TableService } from '../Services/table.service';
import { EADProcess } from '../Classes/EADProcess';
import { MatTableDataSource, MatPaginator, MatSort } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';
import { map, tap, catchError } from 'rxjs/operators';

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.css']
})
export class TableComponent implements OnInit {

  showDataForm = false;

  stringArray: string[] = [];
  eadItems: EADProcess[] = [];

  dataSource: MatTableDataSource<EADProcess>;

  @ViewChild(MatPaginator) paginator: MatPaginator;
  @ViewChild(MatSort) sort: MatSort;

  // which columns the data table needs to display
  columnsToDisplay: string[] = ['id', 'titel', 'EADDraftingStage'];

  constructor(private router: Router,
              private cookieService: CookieService,
              private loginService: LoginService,
              private tableService: TableService,
              private chRef: ChangeDetectorRef) {

  }

  ngOnInit() {
    const $this = this;

    this.getAllEadItems();
  }

  public getAllEadItems() {
    const json: any = {(data omitted for this example)};

    const jsonStringified = JSON.stringify(json);

    this.tableService.getAllEadItems(jsonStringified).subscribe(res => {
      this.convertJsonResultToArray(res);
      this.dataSource = new MatTableDataSource(this.eadItems);
      this.dataSource.paginator = this.paginator;
      this.dataSource.sort = this.sort;
      this.showDataForm = true;
    });
  }

  public convertJsonResultToArray(res: any) {
    this.stringArray = JSON.parse(res);
    for (const eadItem of this.stringArray) {
      const ead = new EADProcess();
      ead.id = eadItem['GUID'];
      ead.titel = eadItem['Title'];
      ead.EADDraftingStage = eadItem['EADDraftingStage'];

      this.eadItems.push(ead);
    }
  }

  public logout() {
    this.cookieService.delete('logindata');
    this.loginService.setLoggedIn(false);
    this.router.navigateByUrl('/login');
  }

}

So to re-iterate, my datatable works fine displaying the data, but now that I wanted to add sorting functionality it doesn't seem to actually sort when I press the header cell(s) I want to sort on. Does anyone see the problem?

Tempuslight
  • 1,004
  • 2
  • 17
  • 34

4 Answers4

32

The problem you have is the *ngIf in the mat-table selector. If you check this.sort you'll see it's undefined. This works :

export class TableComponent implements OnInit { 
sort;
@ViewChild(MatSort) set content(content: ElementRef) {
  this.sort = content;
  if (this.sort){
     this.dataSource.sort = this.sort;

  }
}

I don't remember what answer here in SO I used as a guide for the solution.

wFitz
  • 1,266
  • 8
  • 13
  • Thanks, this worked! Can you please explain why it has to be like this? I'm pretty new to Angular and don't fully understand what this ViewChild does. Does it basically get an element from my View based on the object, but since I don't display it, it gets 'null'/undefined? So what does your method do to prevent this? It does "set content"? Does this wait & trigger when the content is shown on the view? – Tempuslight Jun 28 '18 at 14:37
  • I used as a base https://stackoverflow.com/questions/39366981/angular-2-viewchild-in-ngif#answer-41095677, it says The setter is called once *ngIf becomes true. – wFitz Jun 28 '18 at 15:52
  • 1
    This is brilliant!!! I've literally spent several hours trying to figure out why it wouldn't sort, and this solved it! You're a gentleman and a scholar my friend @wFitz – Trevor Mar 06 '19 at 00:18
  • 1
    Use [hidden] instead of *ngIf in this special case. It won't interfere with your sort. – Pismotality Apr 16 '20 at 19:35
6

This probably is because your sorter isn't correctly bound to your array.

Try using a timeout to delay the binding :

this.convertJsonResultToArray(res);
this.dataSource = new MatTableDataSource(this.eadItems);
setTimeout(() => {
  this.dataSource.paginator = this.paginator;
  this.dataSource.sort = this.sort;

});
this.showDataForm = true;
  • This was my issue. I had to assign the sort during the subscription to the service. – Bob Nov 07 '18 at 14:52
1

If some one still having a issue and requires cleaner approach, they can implement ngAfterViewInit interface and implement it. it's lifecycle hook that is called after Angular has fully initialized a component's view. By referencing questioner's code, TS can be updated by following code.

import { Component, OnInit, ChangeDetectorRef, ViewChild, AfterViewInit  } from '@angular/core';
...
...

export class TableComponent implements OnInit, AfterViewInit {
   ...
   ...
   ngAfterViewInit() {
      this.dataSource.sort = this.sort; // apply sort after view has been initialized.
   }

} 
Harshad Vekariya
  • 972
  • 1
  • 7
  • 28
0
export class SomeComponent implements OnInit, AfterViewInit {       
      public rows = [];
      public dataSource = new MatTableDataSource<SomeElement>([]);    

  constructor(public dialogRef: MatDialogRef<SomeComponent>, @Inject(MAT_DIALOG_DATA) public data: ReportData) {}    

  @ViewChild(MatSort) sort;

  ngOnInit(): void {
    this.rows.push({...});    
    this.rows.push({...});    
    this.rows.push({...});    
    this.dataSource = new MatTableDataSource(this.rows);    
  }    

  ngAfterViewInit(): void {    
      this.dataSource.sort = this.sort;    
  }