I use material table sorting functionality in Angular 18 material design.
The implementation seems to be correct but the sorting is not working when clicking on column header i.e mat-header-cell.
users.component.html
<div class="example-container mat-elevation-z8">
<div class="example-header">
<mat-form-field>
<input matInput (keyup)="applyFilter(getInputValue($event))" placeholder="Search">
</mat-form-field>
</div>
<mat-table [dataSource]="dataSource" matSort>
<!-- Id Column -->
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header> Id </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.id}} </mat-cell>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header="name"> Name </mat-header-cell>
<mat-cell *matCellDef="let user"> {{user.name}} </mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;" (click)="showUserDialog(row)"></mat-row>
</mat-table>
</div>
users.component.ts
import {Component, OnInit, ViewChild, Input} from '@angular/core';
import {UserDetailComponent} from "../user-detail/user-detail.component";
import { MatTableDataSource } from '@angular/material/table';
import {MatDialog} from "@angular/material/dialog";
import {MatSort, Sort} from '@angular/material/sort';
import { User } from '../models/user.model';
import { UserService } from '../services/user.service';
import {ActivatedRoute, Router} from '@angular/router';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
displayedColumns: string[] = ['id', 'name'];
dataSource: MatTableDataSource<User>;
users: User[] = [];
constructor(private userService: UserService,
private dialog: MatDialog,
private route: ActivatedRoute,
private router: Router) {
this.dataSource = new MatTableDataSource<User>();
}
@ViewChild(MatSort) sort!: MatSort;
@Input() filter!: User;
ngOnInit() {
this.userService.getUsers().subscribe((data: User[]) => {
this.users = data;
this.dataSource.data = this.users;
this.dataSource.sort = this.sort;
});
this.dataSource.filterPredicate = (data, filter: string): boolean => {
return data.name.toLowerCase().includes(filter) || data.id.toString() === filter;
};
}
getInputValue(event: Event): string {
const target = event.target as HTMLInputElement;
return target ? target.value : '';
}
applyFilter(filterValue: string) {
filterValue = filterValue.trim().toLowerCase();
this.dataSource.filter = filterValue;
// Update the URL with the new filter value
this.router.navigate([], {
relativeTo: this.route,
queryParams: { find: filterValue },
queryParamsHandling: 'merge'
});
}
showUserDialog(user: User): void {
this.dialog.open(UserDetailComponent, {
width: '500px',
data: { user: user }
});
}
}
main.ts
import {CdkTableModule} from '@angular/cdk/table';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {bootstrapApplication, BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MatAutocompleteModule} from "@angular/material/autocomplete";
import {MatButtonModule} from "@angular/material/button";
import {MatButtonToggleModule} from "@angular/material/button-toggle";
import {MatCardModule} from "@angular/material/card";
import {MatCheckboxModule} from "@angular/material/checkbox";
import {MatChipsModule} from "@angular/material/chips";
import {MatStepperModule} from "@angular/material/stepper";
import {MatDatepickerModule} from "@angular/material/datepicker";
import {MatDialogModule} from "@angular/material/dialog";
import {MatDividerModule} from "@angular/material/divider";
import {MatExpansionModule} from "@angular/material/expansion";
import {MatGridListModule} from "@angular/material/grid-list";
import {MatIconModule} from "@angular/material/icon";
import {MatInputModule} from "@angular/material/input";
import {MatListModule} from "@angular/material/list";
import {MatMenuModule} from "@angular/material/menu";
import {MatNativeDateModule, MatRippleModule} from "@angular/material/core";
import {MatPaginatorModule} from "@angular/material/paginator";
import {MatProgressBarModule} from "@angular/material/progress-bar";
import {MatProgressSpinnerModule} from "@angular/material/progress-spinner";
import {MatRadioModule} from "@angular/material/radio";
import {MatSelectModule} from "@angular/material/select";
import {MatSidenavModule} from "@angular/material/sidenav";
import {MatSliderModule} from "@angular/material/slider";
import {MatSlideToggleModule} from "@angular/material/slide-toggle";
import {MatSnackBarModule} from "@angular/material/snack-bar";
import {MatTabsModule} from "@angular/material/tabs";
import {MatToolbarModule} from "@angular/material/toolbar";
import {MatTooltipModule} from "@angular/material/tooltip";
import {MatTableModule} from '@angular/material/table';
import {provideHttpClient} from "@angular/common/http";
import {RouterModule} from '@angular/router';
import {AppComponent} from "./app/app.component";
import {appConfig} from "./app/app.config";
import {UsersComponent} from "./app/users/users.component";
import {CarsComponent} from "./app/cars/cars.component";
import {UserDetailComponent} from "./app/user-detail/user-detail.component";
import {CarDetailComponent} from "./app/car-detail/car-detail.component";
import { routes } from './app/app.routes';
import {MatSortModule} from "@angular/material/sort";
@NgModule({
exports: [
CdkTableModule,
MatAutocompleteModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatStepperModule,
MatDatepickerModule,
MatDialogModule,
MatDividerModule,
MatExpansionModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatDialogModule,
MatGridListModule,
MatSortModule
]
})
export class DemoMaterialModule {}
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
DemoMaterialModule,
MatNativeDateModule,
ReactiveFormsModule,
RouterModule.forRoot(routes)
],
declarations: [UsersComponent, UserDetailComponent, CarsComponent, CarDetailComponent],
bootstrap: [],
providers: [provideHttpClient()]
})
export class AppModule {}
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
user.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { User } from '../models/user.model';
import {map} from "rxjs/operators";
@Injectable({
providedIn: 'root'
})
export class UserService {
private users: User[] = [
{
"id": 1,
"name": "Tim Johnson",
"cars": [
{
"id": 1,
"make": "BMW",
"model": "3 Series",
"numberplate": "AB12 CDE"
},
{
"id": 2,
"make": "Kia",
"model": "Sorento",
"numberplate": "DE34 FGH"
}
]
},
{
"id": 2,
"name": "Patricia Reed",
"cars": [
{
"id": 3,
"make": "Peugeot",
"model": "308",
"numberplate": "HI56 JKL"
},
{
"id": 4,
"make": "Kia",
"model": "Sorento",
"numberplate": "MN78 PQR"
}
]
},
{
"id": 3,
"name": "Matthew Hall",
"cars": [
{
"id": 5,
"make": "Skoda",
"model": "Octavia",
"numberplate": "ST90 UVW"
},
{
"id": 6,
"make": "Audi",
"model": "A6",
"numberplate": "XY12 ZAB"
}
]
},
{
"id": 4,
"name": "Karen Smith",
"cars": [
{
"id": 7,
"make": "Renault",
"model": "Clio",
"numberplate": "CD34 EFG"
},
{
"id": 8,
"make": "Audi",
"model": "A6",
"numberplate": "GH56 IJK"
}
]
},
{
"id": 5,
"name": "Tom Cruise",
"cars": [
{
"id": 9,
"make": "Volvo",
"model": "XC60",
"numberplate": "LM78 NOP"
}
]
}
];
constructor() { }
getUsers(): Observable<User[]> {
return of(this.users).pipe(
map(users => this.addUserReferenceToCars(users))
);
}
private addUserReferenceToCars(users: User[]): User[] {
// Create a map to store user references by id
const userMap = new Map<number, User>();
// Iterate over users and add them to the map
users.forEach(user => {
userMap.set(user.id, user);
user.cars.forEach(car => {
car.user = user;
});
});
return users;
}
}
I tried commenting out filtering functionality because I though it can interfere with sorting but sorting did not work in this case as well.