Search code examples
angularangular-bootstrapangular-moduleangular-standalone-components

TypeError: Cannot read properties of undefined (reading 'ɵcmp') in Angular 18


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?

enter image description here

How to make it work?

GitHub

UPDATE:

Sort is not working. Updated code. Added demo data to reproduce sort issue easily

enter image description here

UPDATE 2:

Finally made it working: GitHub commit


Solution

  • 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]);
      }
    }
    

    Github Repo