I'm trying to make the sorting of table work for /users
that is defined in UserListComponent
. The sorting functionality was broken by introducing NavbarComponent
.
I tired to switch to standalone components when possible as suggested in this answer. I'm new to Angular and as far as I understand the @NgModule
in DemoMaterialModule
(main.ts) is quite useful because I don't have to import every single material module in every component.
Angular 18 promotes standalone way so it may be a good idea to go this route.
Is this the best fit form my use case? Would it be better to fully switch from my hybrid approach (standalone + modular) to standalone?
How to make it work?
UPDATE:
Sort is not working. Updated code. Added demo data to reproduce sort issue easily
UPDATE 2:
Finally made it working: GitHub commit
During conversion of standalone style to modular style, we are using components in the routing that are not imported in the AppModule that caused the issue mostly.
I converted the entire app to standalone, as was stated in an earlier question.
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 {provideRouter, RouterModule} from '@angular/router';
import {AppComponent} from "./app/app.component";
import {CarListComponent} from "./app/cars-list/car-list.component";
import {CarComponent} from "./app/car/car.component";
import { routes } from './app/app.routes';
import {MatSortModule} from "@angular/material/sort";
import {UserCarsComponent} from "./app/user-cars/user-cars.component";
import {UserComponent} from "./app/user/user.component";
import {CommonModule} from "@angular/common";
import {MatFormFieldModule} from "@angular/material/form-field";
import { NavbarComponent } from './app/navbar/navbar.component';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { UserListComponent } from './app/user-list/user-list.component';
@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,
MatFormFieldModule
]
})
export class DemoMaterialModule {}
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
DemoMaterialModule,
MatNativeDateModule,
ReactiveFormsModule,
RouterModule.forRoot(routes),
RouterModule,
CommonModule
],
declarations: [
AppComponent,
NavbarComponent,
UserListComponent,
UserComponent,
CarListComponent,
CarComponent,
UserCarsComponent,
],
bootstrap: [AppComponent],
exports: [
],
providers: [provideHttpClient()]
})
export class AppModule {}
platformBrowserDynamic().bootstrapModule(AppModule, {
ngZoneEventCoalescing: true
})
.catch(err => console.error(err));
Please find below code changes for fixing sorting, do let me know if any doubts.
import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { MatSort, Sort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Car } from "../models/car.model";
import { ActivatedRoute, Router } from '@angular/router';
import { CarService } from '../services/car.service';
import { FormControl } from "@angular/forms";
import { debounceTime, takeUntil } from "rxjs/operators";
import { Subject } from 'rxjs';
function compare(a: number | string, b: number | string, isAsc: boolean) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
@Component({
selector: 'app-cars',
templateUrl: './car-list.component.html',
styleUrls: ['./car-list.component.css']
})
export class CarListComponent implements OnInit, AfterViewInit {
displayedColumns: string[] = ['id', 'make', 'model', 'numberplate'];
dataSource: MatTableDataSource<Car>;
cars: Car[] = [];
filterControl: FormControl = new FormControl('');
private ngUnsubscribe = new Subject<void>();
@ViewChild(MatSort) sort!: MatSort;
constructor(private carService: CarService,
private route: ActivatedRoute,
private router: Router) {
this.dataSource = new MatTableDataSource<Car>();
}
ngOnInit() {
this.initializeTable();
}
ngAfterViewInit() {
this.route.queryParams.pipe(takeUntil(this.ngUnsubscribe)).subscribe(params => {
const { find } = params;
this.filterControl.setValue(find || '');
this.applyFilter(find || '');
});
}
initializeTable() {
// Fetch cars from service
// this.carService.getCars().subscribe((data: Car[]) => {
// this.cars = data;
// this.dataSource.data = this.cars;
// });
// fake API call
this.cars = [
{
id: 1,
make: 'car1',
model: 'asdf1',
numberplate: '12344',
},
{
id: 2,
make: 'car2',
model: 'asdf2',
numberplate: 'h4r5ty',
},
{
id: 1,
make: 'car33',
model: 'asdf33',
numberplate: 'zxcvfbdf',
},
{
id: 1,
make: 'car45',
model: 'asdf45',
numberplate: 'asdfae',
}
];
this.dataSource.data = this.cars;
// Set filter predicate
this.dataSource.filterPredicate = (data, filter: string): boolean => {
return data.make.toLowerCase().includes(filter) || data.model.toLowerCase().includes(filter) || data.numberplate.toLowerCase().includes(filter);
};
// Subscribe to changes in the filter control with debounce
this.filterControl.valueChanges.pipe(
debounceTime(300),
takeUntil(this.ngUnsubscribe)
).subscribe(value => {
this.applyFilter(value);
});
}
applyFilter(filterValue: string) {
filterValue = filterValue.trim().toLowerCase();
this.dataSource.filter = filterValue;
// Update the URL with the new filter value or remove the parameter if the filter is empty
this.router.navigate([], {
relativeTo: this.route,
queryParams: filterValue ? { find: filterValue } : {},
queryParamsHandling: filterValue ? 'merge' : ''
});
}
applySort(sortState: Sort) {
const data = this.cars.slice();
if (!sortState.active || sortState.direction === '') {
this.dataSource.data = data;
return;
}
this.dataSource.data = data.sort((a, b) => {
const isAsc = sortState.direction === 'asc';
switch (sortState.active) {
case 'id':
return compare(a.id, b.id, isAsc);
case 'make':
return compare(a.make, b.make, isAsc);
case 'model':
return compare(a.model, b.model, isAsc);
case 'numberplate':
return compare(a.numberplate, b.numberplate, isAsc);
default:
return 0;
}
});
const sortField = sortState.active;
const sortDirection = sortState.direction as 'asc' | 'desc';
// Update the URL with the new sort value
this.router.navigate([], {
relativeTo: this.route,
queryParams: { sort: `${sortField}:${sortDirection}` },
queryParamsHandling: 'merge'
});
}
navigateToCarDetail(car: Car): void {
this.router.navigate(['/cars', car.id]);
}
}