Mat-table Sorting Demo not Working
AngularAngular MaterialAngular Problem Overview
I am trying to get the mat-table
sorting to work locally, and while I can get the data to show up as expected, clicking on the header row does not do the sorting as it does on online examples (nothing happens at all).
I am trying to get this demo working locally:
https://material.angular.io/components/sort/overview
https://plnkr.co/edit/XF5VxOSEBxMTd9Yb3ZLA?p=preview
I have generated a new project with Angular CLI, then followed these steps: https://material.angular.io/guide/getting-started
Here are my local files:
app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { MatSort, MatTableModule } from '@angular/material';
import { AppComponent } from './app.component';
import { TableSortingExample } from './table-sorting-example';
@NgModule({
declarations: [
AppComponent,
TableSortingExample,
MatSort
],
imports: [
BrowserModule,
MatTableModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'app';
}
app.component.html
<div style="text-align:center">
<h1>
Welcome to {{title}}!
</h1>
<table-sorting-example></table-sorting-example>
</div>
table-sorting-example.html
<div class="example-container mat-elevation-z8">
<mat-table #table [dataSource]="dataSource" matSort>
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- ID Column -->
<ng-container matColumnDef="userId">
<mat-header-cell *matHeaderCellDef mat-sort-header> ID </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.id}} </mat-cell>
</ng-container>
<!-- Progress Column -->
<ng-container matColumnDef="progress">
<mat-header-cell *matHeaderCellDef mat-sort-header> Progress </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.progress}}% </mat-cell>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="userName">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
</ng-container>
<!-- Color Column -->
<ng-container matColumnDef="color">
<mat-header-cell *matHeaderCellDef mat-sort-header> Color </mat-header-cell>
<mat-cell *matCellDef="let row" [style.color]="row.color"> {{row.color}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>
<!-- Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license -->
table-sorting-example.ts
import {Component, ViewChild} from '@angular/core';
import {DataSource} from '@angular/cdk/collections';
import {MatSort} from '@angular/material';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
/**
* @title Table with sorting
*/
@Component({
selector: 'table-sorting-example',
styleUrls: ['table-sorting-example.css'],
templateUrl: 'table-sorting-example.html',
})
export class TableSortingExample {
displayedColumns = ['userId', 'userName', 'progress', 'color'];
exampleDatabase = new ExampleDatabase();
dataSource: ExampleDataSource | null;
@ViewChild(MatSort) sort: MatSort;
ngOnInit() {
this.dataSource = new ExampleDataSource(this.exampleDatabase, this.sort);
}
}
/** Constants used to fill up our data base. */
const COLORS = ['maroon', 'red', 'orange', 'yellow', 'olive', 'green', 'purple',
'fuchsia', 'lime', 'teal', 'aqua', 'blue', 'navy', 'black', 'gray'];
const NAMES = ['Maia', 'Asher', 'Olivia', 'Atticus', 'Amelia', 'Jack',
'Charlotte', 'Theodore', 'Isla', 'Oliver', 'Isabella', 'Jasper',
'Cora', 'Levi', 'Violet', 'Arthur', 'Mia', 'Thomas', 'Elizabeth'];
export interface UserData {
id: string;
name: string;
progress: string;
color: string;
}
/** An example database that the data source uses to retrieve data for the table. */
export class ExampleDatabase {
/** Stream that emits whenever the data has been modified. */
dataChange: BehaviorSubject<UserData[]> = new BehaviorSubject<UserData[]>([]);
get data(): UserData[] { return this.dataChange.value; }
constructor() {
// Fill up the database with 100 users.
for (let i = 0; i < 100; i++) { this.addUser(); }
}
/** Adds a new user to the database. */
addUser() {
const copiedData = this.data.slice();
copiedData.push(this.createNewUser());
this.dataChange.next(copiedData);
}
/** Builds and returns a new User. */
private createNewUser() {
const name =
NAMES[Math.round(Math.random() * (NAMES.length - 1))] + ' ' +
NAMES[Math.round(Math.random() * (NAMES.length - 1))].charAt(0) + '.';
return {
id: (this.data.length + 1).toString(),
name: name,
progress: Math.round(Math.random() * 100).toString(),
color: COLORS[Math.round(Math.random() * (COLORS.length - 1))]
};
}
}
/**
* Data source to provide what data should be rendered in the table. Note that the data source
* can retrieve its data in any way. In this case, the data source is provided a reference
* to a common data base, ExampleDatabase. It is not the data source's responsibility to manage
* the underlying data. Instead, it only needs to take the data and send the table exactly what
* should be rendered.
*/
export class ExampleDataSource extends DataSource<any> {
constructor(private _exampleDatabase: ExampleDatabase, private _sort: MatSort) {
super();
}
/** Connect function called by the table to retrieve one stream containing the data to render. */
connect(): Observable<UserData[]> {
const displayDataChanges = [
this._exampleDatabase.dataChange,
this._sort.sortChange,
];
return Observable.merge(...displayDataChanges).map(() => {
return this.getSortedData();
});
}
disconnect() {}
/** Returns a sorted copy of the database data. */
getSortedData(): UserData[] {
const data = this._exampleDatabase.data.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 'userId': [propertyA, propertyB] = [a.id, b.id]; break;
case 'userName': [propertyA, propertyB] = [a.name, b.name]; break;
case 'progress': [propertyA, propertyB] = [a.progress, b.progress]; break;
case 'color': [propertyA, propertyB] = [a.color, b.color]; break;
}
let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
let valueB = isNaN(+propertyB) ? propertyB : +propertyB;
return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : -1);
});
}
}
/** Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license */
Does anyone have an idea of why it would show up like the online table but lack the sorting functionality?
Angular Solutions
Solution 1 - Angular
For anyone else who may have this problem: The problem was I didn't read the API reference properly on the angular materials website, the part that said I had to import MatSortModule. After I changed my imports list in app.module.ts to
imports: [
BrowserModule,
MatTableModule,
MatSortModule
],
it worked fine
Solution 2 - Angular
I had a problem that the sorting function was working but it wasn't sorting properly. I realized that matColumnDef
has to have the same name of the property of my class / interface
that I am referencing in matCellDef
.
According to the Angular Material documentation:
> By default, the MatTableDataSource sorts with the assumption that the sorted column's name matches the data property name that the column displays.
For exemple:
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> NAME </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
</ng-container>
The name
in the matColumnDef
directive has to be the same as the name
used in the <mat-cell>
component.
Solution 3 - Angular
If the table is inside *ngIf, it won't be working. It will work if it is changed to [hidden]
Solution 4 - Angular
matColumnDef name and *matCellDef actual value name should be same
Example:
<ng-container matColumnDef="oppNo">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Opportunity Number</th>
<td mat-cell *matCellDef="let element">{{element.oppNo}}</td>
</ng-container>
In my case oppNo is same for matColumnDef name and *matCellDef name and sorting working fine.
Solution 5 - Angular
I also hit this issue. Since you need to wait for the child to be defined, you have to implement and use AfterViewInit
, not onInit.
ngAfterViewInit (){
this.dataSource.sort = this.sort;
}
Solution 6 - Angular
One of the reasons MatSort might not work, is when it is added to a dataSource (i.e. this.dataSource.sort = this.sort
) before it is defined. There can be multiple reasons for this:
-
if you add the sort in ngOnInit. At this point the template is not yet rendered, so the MatSort you get with
@ViewChild(MatSort, { static: true }) sort: MatSort;
is undefined and understandably will not do anything. A solution for this issue is to movethis.dataSource.sort = sort
to ngAfterViewInit. When ngAfterViewInit is called your component is rendered, and MatSort should be defined. -
when you use *ngIf is your template on your table element or one if it's parent elements and this *ngIf causes your table not to be rendered at the moment you try to set the MatSort. For example, if you have
*ngIf="dataSource.data.length > 0"
on your table element (to only render it if there is data present) and you setthis.dataSource.sort = this.sort
right after you setthis.dataSource.data
with your data. The component view will not be rerendered yet, so MatSort will still be undefined.
In order to get MatSort to work and still conditionally show your table you could decide to replace the *ngIf
with [hidden]
as stated in multiple other answers. However, if you want to keep your *ngIf statement you can use the following solution. This solution works for Angular 9, I haven't tested it on previous versions so I'm not sure if it works there.
I found this solution here: https://github.com/angular/components/issues/10205
Instead of putting:
@ViewChild(MatSort) sort: MatSort;
use a setter for matSort. This setter will fire once matSort in your view changes (i.e. is defined the first time), it will not fire when you change your sorting by clicking on the arrows. This will look like this:
@ViewChild(MatSort) set matSort(sort: MatSort) {
this.dataSource.sort = sort;
}
If you have other functions that (programmatically) change the sorting, I'm not sure if it will fire again, I haven't tested this. If you wan't to make sure it only sets the sort if the sort was undefined, you can do something like this:
@ViewChild(MatSort) set matSort(sort: MatSort) {
if (!this.dataSource.sort) {
this.dataSource.sort = sort;
}
}
Solution 7 - Angular
Adding sort within timeout block works for me,
dataSource = new MatTableDataSource(this.articleService.getAllArticles());
setTimeout(() => {
this.tableDataSource.sort = this.sort;
this.tableDataSource.paginator = this.paginator;
});
If you don't want to use lifecykle hooks.
Solution 8 - Angular
I spent hours on this issue. After reading through a number of threads, here are the steps I did.
- As @avern mentioned, you need to import
MatSortModule
. - Make sure you are NOT enclosing the table in a
*ngIf
. Change it to[hidden]
as @zerg recommended. (I don't understand why)
Hope this helps.
Solution 9 - Angular
My solution was to fix several things (basically merging most of the solutions in this page).
Things to check:
BrowserModule, MatTableModule, MatSortModule
Modules should be imported in the root modules file.- Make sure to have used
MatTableDatasource
class and pass your data array in it as a parameter - Make sure your table is not nested in an
*ngIf=....
directive. Use other conditional operations instead (still don't understand why).
Solution 10 - Angular
in your app.module.ts, do the following:
import
import { MatSortModule } from '@angular/material/sort';
then add
imports: [
...
MatSortModule
],
Solution 11 - Angular
I fixed this in my scenario by naming the table data with same name as *matColumnDef For example:
<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
</ng-container>
Instead
<!-- Name Column -->
<ng-container matColumnDef="userName">
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
</ng-container>
Solution 12 - Angular
There were 2 issues for me.
-
The matColumnDef and matCellDef -> names are different
-
I was getting the data from the service. The ngOnInit sort was not working. Replaced with
ngAfterViewInit() { this.dataSource.sort = this.sort; }
Solution 13 - Angular
I found this old blog which helped me get it to work: https://www.jeffryhouser.com/index.cfm/2018/10/23/Five-Reasons-My-ngMaterial-Table-wont-sort
- Make sure to import
MatSortModule
- Specify the
matSort
header - Make sure to wrap your datasource in a
MatTableDataSource
- This is the one that helped me sort it out (get it? sort it out). In the template I was referring to the array directly (
<table mat-table [dataSource]="this.products" matSort>
) but I should have used the datasource object I initialized in the code (<table mat-table [dataSource]="this.dataSource" matSort>
). The datasource is initialized likedataSource = new MatTableDataSource(this.products)
- This is the one that helped me sort it out (get it? sort it out). In the template I was referring to the array directly (
- Tell the data source about your sort, in
ngOnInit
/ngAfterViewInit
- Write your own sort, if you do not want to use
MatTableDataSource
Solution 14 - Angular
For anyone that is confused about these namings having to be equal, I did some testing:
This will work (the name of the property is the same as the column def):
<ng-container matColumnDef="version">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Version </th>
<td mat-cell *matCellDef="let element"> {{element.version}} </td>
</ng-container>
displayedColumns: string[] = ['version']
This will NOT work (the name of the property is not the same as the column def):
<ng-container matColumnDef="version2">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Version </th>
<td mat-cell *matCellDef="let element"> {{element.version}} </td>
</ng-container>
displayedColumns: string[] = ['version2']
Fyi, this also does NOT work (the length of a property):
<ng-container matColumnDef="length">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Version </th>
<td mat-cell *matCellDef="let element"> {{element.ids.length}} </td>
</ng-container>
displayedColumns: string[] = ['length']
And neither does this:
<ng-container matColumnDef="ids.length">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Version </th>
<td mat-cell *matCellDef="let element"> {{element.ids.length}} </td>
</ng-container>
displayedColumns: string[] = ['ids.length']
Solution 15 - Angular
For me replacing *ngIf with [hidden] attribute for mat-table tag worked. How to post this one as a bug to Angular Material community?
Solution 16 - Angular
I found multiple answers to this question, but implementing them individually did not give me any results. So I tried to merge answers and it worked.
Firstly, I added ViewChild sort inside NgAfterViewInit interface. (It was initially inside a function that was called through NgOnInit
ngAfterViewInit(){
this.tableData.sort = this.sort;
}
For the second step i changed the *ngIf inside a container to [hidden]. I do get an error that says that the value is not loaded. But it is not a major one to be concerned about till now.
before
<div class="mat-elevation-z0 container-fluid" *ngIf={some boolean resultant condition}>
after
<div class="mat-elevation-z0 container-fluid" [hidden] = {negation of your boolean expression}>
psst.. You can also consider adding a loading spinner while your table is being loaded through mat-footer to above the above bs.
<ng-container matColumnDef="loading">
<mat-footer-cell *matFooterCellDef colspan=6>
<div class="uploader-status">
<mat-spinner strokeWidth="25" [diameter]="100" title="Server Starting" ></mat-spinner>
</div>
</mat-footer-cell>
</ng-container>
<mat-footer-row *matFooterRowDef="['loading']" [ngStyle]="{'display': (this.candidateService.candidateRecords!=null) ? 'none':'block'}"></mat-footer-row>
Solution 17 - Angular
If your table is inside an *ngIf and you think it has something to do with it not sorting your table, then specifying your own sortingDataAccessor
function might solve the issue as it did for me. I have my table inside couple of *ngIfs and taking it out of those *ngIfs did not make sense:
`ngAfterViewInit(): void {
this.matchesDataSource.sort = this.sort;
this.matchesDataSource.sortingDataAccessor = previewMatchSortingFn;
}`
`export function previewMatchSortingFn(item: Match, header: string): string | number {
switch (header) {
case 'home':
return item.homeTeam.name;
case 'away':
return item.awayTeam.name;
case 'date':
if (item.dateTime) {
// this will return the number representation of the date
return item.dateTime.valueOf();
}
return;
default:
break;
}
}`
Solution 18 - Angular
The below code was worked for me perfectly,
@ViewChild(MatSort) set matSort(sort: MatSort) {
if (!this.dataSource.sort) {this.dataSource.sort = sort;}}
Solution 19 - Angular
The main reasons why mat-sort & mat-paginator doesn't work are
- Modules MatSortModule & MatPaginatorModule are not imported
- Table is under *ngIf condition
- matColumnDef should be the same as matCellDef and displayedColumns array.
Solution 20 - Angular
After spending time on this for weeks. I found out you the following
-
You need to import MatSortModule in your app.module.ts.
> imports: [ > ... > MatSortModule > ],
>
-
Define the sort and paginator in ngViewInit
>
ngAfterViewInit(): void {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
this.dataSource.paginator?._changePageSize(400)
}
Solution 21 - Angular
If you read all answers until here and nothing helped, maybe you have the same problem I had.
The problem was that my MatTableDataSource
object
dataSource = new MatTableDataSource<StbElement>(ELEMENT_DATA);
Was used in the html file without this
.
Changing:
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
To:
<table mat-table [dataSource]="this.dataSource" matSort class="mat-elevation-z8">
Fixed the problem.
Solution 22 - Angular
In addition to all the previous answers, sometimes the table isn't visible at the time of data retrieval. For example, I had to display a table with MatSort and MatPaginator inside of a modal/dialog. Therefore, I had to pass in the elements through their respective Output emitter functions like so:
<... matSort #sort="matSort" (matSortChange)="sortData(sort)">
<... #paginator (page)="changePaginator(paginator)">
And in the typescript:
@ViewChild(MatSort, { static: false }) set sort(s: MatSort) {
this.dataSource.sort = s;
}
@ViewChild(MatPaginator, { static: false }) set paginator(p: MatPaginator) {
this.dataSource.paginator = p;
}
sortData(sort: MatSort) {
this.sort = sort;
}
changePaginator(paginator: MatPaginator) {
this.paginator = paginator;
}
Make sure to set the paginator's default values in its input directives since the above code will set the elements after paginating, i.e: [pageSize]="5" [length]="dataSource?.data?.length"
. Please use this as a last resort to all previous solutions.
Solution 23 - Angular
Actually matColumnDef name(i.e column name) and your Class/Interface property name should be equal to make it work.
Sometimes we can't change our Class/Interface property name, in that case, we can implement custom sorting as below.
let say your columns as ['id', 'name'] and
your class/interface as ['userId', 'name']
if we perform sorting on the 'id' column it won't work. Try with the custom sorting
this.dataSource.sortingDataAccessor = (item,property)=>{
// where item is your class/interface data
// where property is your column name
switch(property){
case 'id' : return item.userId
default: return item[property];
}
}
Solution 24 - Angular
My solution for this problem is as below -
1. These two lines will go in the same order.
this.dataSource = new MatTableDataSource(myRowDataArray);// this dataSource is used in table tag.
this.dataSource.sort = this.sort;
2. Pass MatTableDataSource object in [dataSource]
<table mat-table [dataSource]="dataSource">
// rest of the table definition here
</table>
3. By default, the MatTableDataSource sorts with the assumption that the sorted column's name matches the data property name that the column displays.
Example -
<ng-container matColumnDef="date" >
<th class="headers" mat-header-cell *matHeaderCellDef mat-sort-header>Date</th>
<td class="data" mat-cell *matCellDef="let row">{{row.date|date}}</td>
</ng-container>
4. If the table is inside *ngIf,then replace it with [hidden] or some other filter.
I missed the 2nd point.
Cheers!
Solution 25 - Angular
This issue mainly happens when sort
is initialized before the dataSource
. In the demo found here the dataSource
is initialized statically as a result no issue happens. When you have to fetch data asynchronously, though, you need to wait for the response from API call to reach and get assigned to the dataSource
before you initilize the sort
instance variable.
Solution 26 - Angular
I don't know the reason; but putting this.dataSource.sort = this.sort;
assignment to ngAfterViewInit()
method didn't work. Even I confirmed this function is getting hitting after page loaded still was not working.
The solution for me was putting sort assignment in ngOnInit()
method.
ngOnInit(): void {
this.service.getAllAudits().subscribe(result => {
this.dataSource = new MatTableDataSource(result);
this.dataSource.sort = this.sort;
});
}
Solution 27 - Angular
changing
@ViewChild('matsort') sort: MatSort;
to
@ViewChild(matSort) sort: MatSort
did it for me must be the same
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8" matSort matSortActive="name" matSortDirection="asc">
Solution 28 - Angular
Its working for me..
@ViewChild('sortConfigTable', { static: false }) sortConfigTable: MatSort;
after data initial assign
this.dataSource.data = ds;
setTimeout(() => {
if (this.sortConfigTable) {
this.dataSource.sort = this.sortConfigTable;
}
}, 1000);
Solution 29 - Angular
See if you have any javascript errors in the console. It could be that some other thing failed before your sorting initialized.