mat-sort not working on mat-table
Asked Answered
E

9

22

My mat-table is working fine, but when adding mat-sort following the official api documentation, it fails at the ngAfterViewInit with the following message

Cannot set property 'sort' of undefined at ViewFeedbackComponent.ngAfterViewInit

There is already a SO post on this issue (see following link) Mat-table Sorting Demo not Working but I still am not able to get it working.

Does somebody spot the issue? The official example works with a "static" MatTableDataSourcedefined in the component itself, I am querying from my back-end, however.

Any help is greatly appreciated!

MatSortModule is already imported in app.module.ts, mat-sort-header directives are applied to the columns and the ngAfterViewInit is already exactly like in the official example...

import {  Component,  OnInit,  ViewEncapsulation,  ViewChild,  AfterViewInit} from '@angular/core';
import {  Feedback} from '../../../../../models/feedback';
import {  FeedbackService} from '../../services/feedback.service';
import {  MatTableDataSource,  MatSort} from '@angular/material';


@Component({
  selector: 'app-view-feedback',
  templateUrl: './view-feedback.component.html',
  styleUrls: ['./view-feedback.component.css'],
  encapsulation: ViewEncapsulation.Emulated
})
export class ViewFeedbackComponent implements OnInit, AfterViewInit {

  feedbacks: Feedback[] = [];
  showSpinner: boolean = true;
  displayedColumns: String[] = [
    'id',
    'user',
    'timestamp',
    'stars'
  ];
  dataSource: MatTableDataSource < Feedback > ;

  @ViewChild(MatSort) sort: MatSort;

  constructor(private _feedbackService: FeedbackService) {}

  ngOnInit() {
    this._feedbackService.getFeedback.subscribe(
      res => {
        this.feedbacks = res;
        this.dataSource = new MatTableDataSource(this.feedbacks);
      }
    );

  }

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


}

<div class="mat-tbl-container mat-elevation-z8">
  <mat-table #tbl [dataSource]="dataSource" matSort>

    <!-- column definitions -->
    <ng-container matColumnDef="id">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Id</mat-header-cell>
      <mat-cell *matCellDef="let r"> {{r._id}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="user">
      <mat-header-cell *matHeaderCellDef mat-sort-header>User Id</mat-header-cell>
      <mat-cell *matCellDef="let r"> {{r.user}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="timestamp">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Date</mat-header-cell>
      <mat-cell *matCellDef="let r"> {{r.timestamp}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="stars">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Stars</mat-header-cell>
      <mat-cell *matCellDef="let r"> {{r.stars}} </mat-cell>
    </ng-container>

    <!-- tbl display settings -->
    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>

  </mat-table>
Euripides answered 11/2, 2018 at 13:6 Comment(1)
Possible duplicate of Multiple mat-table with MatSort within the same componentPatrick
R
24

Problem is that next piece of code

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

is happen before you actually got your table in subscription here:

  ngOnInit() {
    this._feedbackService.getFeedback.subscribe(
      res => {
        this.feedbacks = res;
        this.dataSource = new MatTableDataSource(this.feedbacks);
      }
    );

  }

As a possible solution, you could synchronize ngAfterViewInit call and getFeedback subscription via Observable.zip. Please refer to RxJS zip documentation

Revile answered 11/2, 2018 at 21:16 Comment(3)
Thank you for pointing this out to me. Example 1 @ was the solution as you correctly suggested learnrxjs.io/operators/combination/zip.htmlEuripides
Please, post the solution if you can ! Thanks.Presumptuous
Will you please post the solution @RevileMayemayeda
G
10

The simple solution for this is instead of declaring sorting in ngAfterViewInit, declare after you get the result from the "this._feedbackService.getFeedback.subscribe".

The solution is as below.

  ngOnInit() {
    this._feedbackService.getFeedback.subscribe(
      res => {
        this.feedbacks = res;
        this.dataSource = new MatTableDataSource(this.feedbacks);

        this.dataSource.sort = this.sort; //this will solve your problem

      }
    );

  }

The above one works perfectly for me.

Thanks,

Gabriello answered 29/8, 2019 at 4:54 Comment(0)
L
8

For matSort to work the type defination is important, at least that's what I found. So with type as any in the code : dataSource: MatTableDataSource; it will not work. There has to be a type defined here to make it work, try to define a interface and pass it in the generics of MatTableDataSource . Also matColumnDef has to match the property name of the defined type.

Ledaledah answered 27/7, 2020 at 23:38 Comment(2)
Thank you so much!! My matColumnDef did not match my property name!!! This saved my life!Obvert
Same.. "matColumnDef has to match the property name of the defined type"Guideboard
V
7

Make sure you added {static: false} when you are getting data from API

@ViewChild(MatSort, {static: false}) sort: MatSort;
Victor answered 21/6, 2020 at 10:55 Comment(0)
W
0

I am using the former sort method example from an older version of angular-material. The latest angular-material sort example uses ngAfterViewInit() to call sort this.dataSource.sort = this.sort; I was unable to get sort to work using the new example. The older sort method uses extends DataSource . I was able to import DataSource using a new path `import { DataSource } from '@angular/cdk/table';

import { Component, ViewChild, Inject, OnInit, ElementRef } from '@angular/core';
import { MatTableDataSource, MatSort } from '@angular/material';
import { DataSource } from '@angular/cdk/table';
import { Observable } from 'rxjs/Observable';
import { HttpClient, HttpResponse, HttpHeaders, HttpRequest} from '@angular/common/http';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';

export interface Data {}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {

  myData: Array < any > ;
  displayedColumns = ['id', 'name'];

  dataSource: MyDataSource;

  @ViewChild(MatSort) sort: MatSort;

  constructor(private http: HttpClient) {}

 getData() {
    let url = 'https://api.mydatafeeds.com/v1.1/cumulative_player_data.json?';
    let headers = new HttpHeaders({ "Authorization": "123ykiki456789123456" });
    this.http.get(url, {headers})
      .subscribe(res => {
        this.myData = res;
        this.dataSource = new MyDataSource(this.myData, this.sort);
      });
  }

  ngOnInit() {
    this.getData();
  }
}

export class MyDataSource extends DataSource < any > {
  constructor(private dataBase: Data[], private sort: MatSort) {
    super();
  }
  /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable < Data[] > {
    const displayDataChanges = [
      Observable.of(this.dataBase),
      this.sort.sortChange,
    ];

    return Observable.merge(...displayDataChanges).map(() => {
      return this.getSortedData();
    });
  }

  disconnect() {}

  /** Returns a sorted copy of the database data. */
  getSortedData(): Data[] {
    const data = this.dataBase.slice();
    if (!this.sort.active || this.sort.direction == '') { return data; }

    return data.sort((a, b) => {

      let propertyA: number | string = '';
      let propertyB: number | string = '';

      switch (this.sort.active) {
        case 'id':
          [propertyA, propertyB] = [a.id, b.id];
          break;
        case 'name':
          [propertyA, propertyB] = [a.name, b.name];
          break;

      }

      let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
      let valueB = isNaN(+propertyB) ? propertyB : +propertyB;

      return (valueA < valueB ? -1 : 1) * (this.sort.direction == 'asc' ? 1 : -1);
    });

  }

}
<mat-table #table [dataSource]="dataSource" matSort>
  <ng-container matColumnDef="id">
    <mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
    <mat-cell *matCellDef="let data"> <b>{{data.id}}.</b>
    </mat-cell>
  </ng-container>
  <ng-container matColumnDef="name">
    <mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
    <mat-cell *matCellDef="let data"> <b>{{data.name}}.</b>
    </mat-cell>
  </ng-container>
  <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
  <mat-row *matRowDef="let data; columns: displayedColumns;"></mat-row>
</mat-table>
Whinstone answered 12/4, 2018 at 21:35 Comment(1)
I could make it work with same pattern but mattabledatasource which contains more functionalities amd with "standard" sort from viewChild if it helpsThadthaddaus
T
0

I redefined the connect mehod inheriting from MatTableDataSource bad idea...

Thadthaddaus answered 10/3, 2019 at 23:7 Comment(0)
P
0

In angular 7 use the following example for two Paginators but replace paginator with sort.

https://stackblitz.com/edit/data-table-multiple-data-source

Here is a duplicate of this question:

Multiple mat-table with MatSort within the same component

Patrick answered 21/5, 2019 at 15:30 Comment(0)
M
0

You don't need to re-initalize the object, you can just change the data

  ngOnInit() {
    this._feedbackService.getFeedback.subscribe(
      res => {
        this.feedbacks = res;

        // this.dataSource = new MatTableDataSource(this.feedbacks);
        // this.dataSource.sort = this.sort; //this will solve your problem
        // becomes
        this.dataSource.data = this.feedbacks

      }
    );

  }
Mancy answered 11/12, 2021 at 7:29 Comment(0)
H
0

It works..

    this.data = res.content;
    this.dataSource = new MatTableDataSource(this.data)
    
    this.dataSource.sort = this.sort;

it should be written right after the MatTableDataSource line,

instead of written as below

     ngAfterViewInit() {
        this.dataSource.sort = this.sort
        
      }

or 

    ngOnInit(){
    this.dataSource.sort = this.sort
    }
Hexapod answered 15/2, 2023 at 7:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.