42

I'm having trouble with importing matSort into my matTable.

I'm providing you with my code:

dashboard.component.ts

import {Component, ViewChild} from '@angular/core';
import {UserService} from "../user.service";
import {DohvatMantisaService} from "../dohvat-mantisa.service";
import {Mantisi} from "../Mantisi";
import {Observable} from "rxjs/Observable";
import {DataSource} from "@angular/cdk/collections";
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import {MatSort} from '@angular/material';
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent{
  displayedColumns = ['projekt', 'manits', 'kategorija','ozbiljnost','datum_prijave','postavio','odjel','postavio','naziv','status','planirana_isporuka'];
  constructor(private user:UserService, private dohvatMantisa:DohvatMantisaService) {
  }
  dataSource: TableDataSource | null;
  mantisi: Mantisi[];
  name = this.user.getUsername();
  @ViewChild(MatSort) sort: MatSort;
  ngOnInit() {
    this.dataSource = new TableDataSource(this.dohvatMantisa,this.sort);
  }
}

export class TableDataSource extends DataSource<Mantisi>{
  constructor(private mantisiDS: DohvatMantisaService,private sort: MatSort){
    super();
  }
  connect(): Observable<Mantisi[]> {
    const displayDataChanges = [
      this.mantisiDS.dohvatiMantise(),
      this.sort.sortChange,
    ];

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

  getSortedData(): Mantisi[]{
    const data = this.mantisiDS.prikazMantisa().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 'projekt': [propertyA, propertyB] = [a.projekt, b.projekt]; break;
        case 'manits': [propertyA, propertyB] = [a.manits, b.manits]; break;
        case 'kategorija': [propertyA, propertyB] = [a.kategorija, b.kategorija]; break;
        case 'ozbiljnost': [propertyA, propertyB] = [a.ozbiljnost, b.ozbiljnost]; break;
        case 'datum_prijave': [propertyA, propertyB] = [a.datum_prijave, b.datum_prijave]; break;
        case 'postavio': [propertyA, propertyB] = [a.postavio, b.postavio]; break;
        case 'naziv': [propertyA, propertyB] = [a.naziv, b.naziv]; break;
        case 'status': [propertyA, propertyB] = [a.status, b.status]; break;
        case 'planirana_isporuka': [propertyA, propertyB] = [a.planirana_isporuka, b.planirana_isporuka]; break;
      }

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

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

This is my service: dohvatMantisaService

import { Injectable } from '@angular/core';
import {Http, Response, RequestOptions, Headers} from "@angular/http";
import {Observable} from "rxjs/Observable";
import {Mantisi} from "./Mantisi";
import 'rxjs';
import 'rxjs/operator/map';
import 'rxjs/operator/do';

@Injectable()
export class DohvatMantisaService {
  constructor(public http:Http) { }
  result: Mantisi[];
  dohvatiMantise(): Observable<Mantisi[]>{
    return this.http.get('/mantis/getMantisReport').map(this.extractData);
  }
  private extractData(response: Response) {
    let body = response.json();
    return body || {};
  }

  prikazMantisa(): Mantisi[]{
    this.dohvatiMantise().subscribe(res => this.result = res);
    return this.result;
  }
}

And im providing you with my dashboard.component.html matSort row:

<mat-table #table [dataSource]="dataSource" class="example-table" matSort>

The main problem here is that data is loaded and acquired from http request but I'm getting errors in console something like this:

Cannot read property 'sortChange' of undefined at TableDataSource.webpackJsonp.../../../../../src/app/dashboard/dashboard.component.ts.TableDataSource.connect (dashboard.component.ts:36) at MatTable.webpackJsonp.../../../cdk/esm5/table.es5.js.CdkTable._observeRenderChanges (table.es5.js:602) at MatTable.webpackJsonp.../../../cdk/esm5/table.es5.js.CdkTable.ngAfterContentChecked (table.es5.js:522)

It tells me that this.sort.sortChange is undefined. I searched all bit of internet and couldn't find proper answer. Hope you can help me with that.

HaaLeo
  • 10,065
  • 3
  • 44
  • 55
Vinko Vorih
  • 2,112
  • 2
  • 14
  • 22

14 Answers14

115

If you use "*ngIf" to initialy hide the table container in your template, the viewchield will be undefined.

starball
  • 20,030
  • 7
  • 43
  • 238
neuralprocessing
  • 1,179
  • 2
  • 6
  • 8
74

I just had this problem, and all I had to do was making sure you're referencing your matSort viewChild correctly and make sure you have added the MatSortModule in your module.ts file in the imports area.

Like the following:

@NgModule({
  imports: [
    MatSortModule,
    MatTableModule,
  ]
)}
frunkad
  • 2,433
  • 1
  • 23
  • 35
Mike June Bug Captain
  • 1,013
  • 1
  • 7
  • 15
  • 4
    It's helpful but it's already imported. Without module import you would get errors like "Have you consider implementing matSort into declaration of @NgModule?". Also, I've posted solution so you can check it. – Vinko Vorih Oct 23 '17 at 06:40
  • 6
    Actually, I didn't get any errors without the module import. It just failed to work because MatSort was undefined at the time I tried to set the `sort` variable. – MFB Feb 15 '18 at 02:15
  • 2
    Google ain't that good at writing docs such that, by merely following their examples, their code just works. This was my problem. Thanks. – Hlawuleka MAS Aug 06 '19 at 08:58
  • 2
    Just needed to add the MatSortModule to the NgModule imports. After doing that it started working. – Edu Aug 13 '19 at 11:18
20

I've found solution for this problem.

The main problem was that table was rendered before data arrived. I've loaded data first and sent it like source to dataSource constructor. After it, problem disappeared. That's the thing with async http.get and services.

Thanks for you help.

fetchData(id){
  this.fetch.getProcesses(id).subscribe(result => {
    this.processes = result;
    this.dataSource = new TableDataSource(this.processes);
    Observable.fromEvent(this.filter.nativeElement, 'keyup')
      .debounceTime(150)
      .distinctUntilChanged()
      .subscribe(() => {
        if (!this.dataSource) {
          return;
        }
        this.dataSource.filter = this.filter.nativeElement.value;
      });
  });
}




export class TableDataSource extends DataSource<Proces>{
  _filterChange = new BehaviorSubject('');
  get filter(): string {
    return this._filterChange.value;
  }
  set filter(filter: string) {
    this._filterChange.next(filter);
  }
  filteredData: any =[];

  constructor(private processes:Proces[]){
    super();
  }
  connect(): Observable<Proces[]>{
    const displayDataChanges = [
      this.processes,
      this._filterChange,
    ];
    return Observable.merge(...displayDataChanges).map(() => {
      this.filteredData = this.processes.slice().filter((item: Proces) => {
        const searchStr = (item.name).toLowerCase();
        return searchStr.indexOf(this.filter.toLowerCase()) !== -1;

      });
      return this.filteredData;
    })
  }
  disconnect(){}
}



      <div class="header">
    <mat-form-field floatPlaceholder="never">
      <input matInput #filter placeholder="Pretraži procese">
    </mat-form-field>
  </div>
  <mat-table #table [dataSource]="dataSource" class="table-mantis" matSort>

    <ng-container matColumnDef="col1">
      <mat-header-cell *matHeaderCellDef class="table-header-cell" mat-sort-header> Name</mat-header-cell>
      <mat-cell *matCellDef="let row" class="example-cell" > {{row.nazivProcesa}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="col2">
      <mat-header-cell *matHeaderCellDef class="table-header-cell" mat-sort-header> Cell 2</mat-header-cell>
      <mat-cell *matCellDef="let row" class="example-cell" > {{row.nazivVlasnika
        }} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="col3">
      <mat-header-cell *matHeaderCellDef class="table-header-cell" mat-sort-header> Cell 3</mat-header-cell>
      <mat-cell *matCellDef="let row" class="example-cell" > {{row.interniAkt}} </mat-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns" class="example-header-row"></mat-header-row>
    <mat-row class="example-row" *matRowDef="let row; columns: displayedColumns;"></mat-row>
  </mat-table>
Vinko Vorih
  • 2,112
  • 2
  • 14
  • 22
18

I had the same issue and fixed it by adding below:

@ViewChild(MatSort) set matSort(ms: MatSort) {
this.sort = ms;
this.setDataSourceAttributes();
 }

@ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
 this.paginator = mp;
this.setDataSourceAttributes();
 }

setDataSourceAttributes() {
  this.dataSource.paginator = this.paginator;
  this.dataSource.sort = this.sort;
  }
Ahmed
  • 191
  • 1
  • 7
  • This really helped me. I'm not doing the paginator, but I was having an issue with sorting after a refresh. – landers May 07 '19 at 14:45
  • 2
    This worked, thanks. For Angular 8 I just had to add options `static: false` - `@ViewChild(MatSort, { static: false }) set matSort(ms: MatSort)` – Richard Dunn Nov 01 '19 at 12:48
  • 1
    { static: false } is missing part when your dataSource is async! – pinarella Apr 23 '20 at 13:14
  • I am getting error on " this.sort=ms" and "this.paginator=mp. – Saqib Farooq Jun 20 '20 at 10:16
  • This solved my issue. For some reason at the time of ngAfterViewInit my sort was undefined. So the property setter allowed me to capture it when it eventually did get set. – Shawn Aug 10 '22 at 18:20
8

With Angular 8 and Material 6.4.1 if you waiting to receive a data from a service the simplest and best way to fix the MatSort and MatPaginator is to set the static: false instead of true, like this:

@ViewChild(MatSort, { static: false }) sort: MatSort;
@ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;

In my case is an ngOnChanges call for data.

George Cs.
  • 319
  • 5
  • 4
  • 1
    Putting `this.dataSource.sort = this.sort` in `ngOnChanges` is what worked for me with an async MatTableDataSource. Also, `{static: false}` is default so you shouldn't have to explicitly declare it. – annmarie-switzer Mar 15 '21 at 13:46
6

So it's happening because of your this.sort.sortChange method is getting called before your child view Initialize

You Just need to implement your class from AfterViewInit instead of OnInit

And use ngAfterViewInit life cycle method

export class ApplicantListComponent implements AfterViewInit {
    @ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;
    @ViewChild(MatSort, { static: false }) sort: MatSort;

    constructor() {}

    ngAfterViewInit() {
        this.sort.sortChange.subscribe(() => {
            this.paginator.pageIndex = 0;
            this.paginator.pageSize = this.pageSize;
        });
} 
Community
  • 1
  • 1
Vishnu Mishra
  • 3,683
  • 2
  • 25
  • 36
4

I had a similar issue when passing async data from a container layer to the component layer. Here's my solution

Because the element started off with no data, there's no use to put a constructor or an OnInit as the original example does. Only After the data stream comes in (async) I implement the sort and paginator in a setter, and everything worked perfectly.

In my template:

<div class="card">

    <div class="card-header">
      <h4>{{pageTitle}}</h4>
      <mat-form-field>
        <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
      </mat-form-field>
    </div>

    <div class="card-body p-0">    
      <div class="mat-elevation-z8">
        <table mat-table [dataSource]="dataSource" matSort>

          <ng-container matColumnDef="projectNumber">
            <th mat-header-cell *matHeaderCellDef mat-sort-header> Werf Nummer </th>
            <td mat-cell *matCellDef="let row"> W{{row.projectNumber}} </td>
          </ng-container>

          <ng-container matColumnDef="description">
            <th mat-header-cell *matHeaderCellDef mat-sort-header> Omschrijving </th>
            <td mat-cell *matCellDef="let row"> {{row.description}} </td>
          </ng-container>

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

        <mat-paginator [pageSizeOptions]="[10,20,100]"></mat-paginator>
      </div>      
    </div>
  </div>

In my angular component class i have

    export class ProjectListComponent {
      pageTitle = 'Projects';
      displayedColumns: string[] = ['projectNumber', 'description'];
      dataSource: MatTableDataSource<Project>;

      @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
      @ViewChild(MatSort, {static: true}) sort: MatSort;
      @Input() set projects(value: Project[]){
        this.dataSource = new MatTableDataSource(value);
        this.dataSource.paginator = this.paginator;
        this.dataSource.sort = this.sort;
      }
      @Input() selectedProject: Project;
      @Output() selected = new EventEmitter<Project>();

      applyFilter(filterValue: string) {
        this.dataSource.filter = filterValue.trim().toLowerCase();

        if (this.dataSource.paginator) {
          this.dataSource.paginator.firstPage();
        }
      }
    }

I should mention something else that's really interesting about the filtering option. Note that project here only gets used for two fields but the interface is actually more fields than that, for instance it has an ID. Even though I'm not showing the ID, I can filter by ID with the above code which is pretty awesome.

    export interface Project {
        id: string;
        projectNumber: string;
        description: string;
        activePeriods: ProjectActivePeriod[];
    }

Also note that it's obligated that the matColumnDef="projectNumber" reference in you displayedColumns has to be the exact name of a property in the interface. If I had used 'ProjectNumber', the sort would not work.

I build this based on the info of

James D
  • 1,975
  • 2
  • 17
  • 26
2
<table mat-table matSort #empTbSort="matSort" [dataSource]="dataSource">
@ViewChild('empTbSort') sort = new MatSort();

 
ngOnInit(): void {
  // async call
  this.dataSource = new MatTableDataSource<any>(data);
  // make sure to put you sore after init dataSource 
  this.dataSource.sort = this.sort;
}

How Mat-table sort works? MatTableDataSource sorts the data by matching the column name(matColumnDef) with the table record property name.

displayedColumns: string[] = ['ID', 'firstname', 'lastname','EmAil'];

export interface Employee 
{
  ID: number,   
  firstname:string, 
  lastname:string,  
  EmAil:string,
}

The property name should match with the matColumnDef, otherwise, the table column will not sort.

read more: https://www.angularjswiki.com/material/mat-table-sort/

Hberdous
  • 64
  • 3
1

I had the same issue,

There are a couple of reasons for this issue

  1. Make sure you have included MatSortModule module in your App module
 import { MatSortModule, MatPaginatorModule, MatTableModule } from '@angular/material';

 @NgModule({
  imports: [MatSortModule, MatPaginatorModule, MatTableModule],
  exports: []
})
  1. Make sure you have subscribed sort change in the ngAfterViewInit
  ngAfterViewInit() {
    this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0 );
  }
Akshay Sharma
  • 1,042
  • 1
  • 8
  • 21
1

In addition to all other answers, if you have multiple tables with sorting in your template, you need to define unique matSort as shown below:

Template:

<mat-table [dataSource]="firstDataSource" matSort #firstSort="matSort">

Component:

@ViewChild('firstSort', { static: false }) firstSort: MatSort;

ngAfterViewInit() {
   this.firstDataSource.sort = this.firstSort;
}
Megabit
  • 434
  • 4
  • 10
0

If the data is within an object, sorting will not work either. E.g.:

interface: Name { firstName: string; lastName: string; }
interface: User { name: Name; }

{{ user.Name.firstName }}

Sorting doesn't work in this case. It needs to be flat like: {{ user.firstName }}

Additionally the property name and column names must match.

sevenam
  • 592
  • 1
  • 8
  • 21
0

For me, I had forgot to put matSort prop on <table/>.

<table mat-table matSort [dataSource]="dataSource">
Muslem Omar
  • 1,021
  • 12
  • 12
0

For me, even though I declared

dataSource: MatTableDataSource<MyRowEntity>

I was still setting it to an array (which would require an explicit call to matTableChild.renderRows())

// renderRows not called automatically onSortChange
dataSource = [{name: 'hello'}, {name: 'world'}] 

Solution:

// renderRows called automatically onSortChange
this.dataSource = new MatTableDataSource(myRowEntities); 

P.S. The original question does not have this error, but I figured others may

Connor
  • 670
  • 3
  • 9
  • 29
0

I update my data from and external source and for some reason when I changed my data variable the table would not update. I then watched for changes in ngOnChanges and updated the data variable then, this displayed my table but did not allow me sort. Here is what finally worked for me:

  dataSource: MatTableDataSource<BasicTableData>;

  constructor() { }

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

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

  ngOnChanges(changes: SimpleChanges): void {
    if(changes['data'].currentValue !== changes['data'].previousValue && !changes['data'].firstChange){
      this.dataSource.data = changes['data'].currentValue;
    }
  }
Hozeis
  • 1,542
  • 15
  • 36