Angular 5 Material Data Table sorting not working
Asked Answered
S

4

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?

Slaty answered 28/6, 2018 at 10:7 Comment(1)
can you create stackblitz with your code?Oshaughnessy
G
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.

Gramnegative answered 28/6, 2018 at 13:28 Comment(4)
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?Slaty
I used as a base #39367481, it says The setter is called once *ngIf becomes true.Gramnegative
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 @GramnegativeParenteau
Use [hidden] instead of *ngIf in this special case. It won't interfere with your sort.Chang
B
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;
Bays answered 28/6, 2018 at 10:25 Comment(1)
This was my issue. I had to assign the sort during the subscription to the service.Dexamyl
K
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.
   }

} 
Kittrell answered 11/4, 2019 at 11:30 Comment(0)
G
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;    
  }   
Gynaeco answered 11/9, 2020 at 10:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.