Search code examples
angularangular-materialfrontendangular-material-7

How can I solve this Error and why it occured?


Implementing AngularMaterial Table with sorting headers I'm getting this Error in console:

ERROR TypeError: Cannot set property 'sort' of undefined at

and this Error in Terminal:

error TS2740: Type 'MatTableDataSource' is missing the following properties from type 'Employee[]': length, pop, push, concat, and 24 more.

error TS2322: Type 'MatSort' is not assignable to type '(compareFn?: (a: Employee, b: Employee) => number) => Employee[]'.

Type 'MatSort' provides no match for the signature '(compareFn?: (a: Employee, b: Employee) => number): Employee[]'.

I cannot get what it wants from me. I would appreciate any hints!

I've used the code from AngularMaterial Documentation except for an interface for Data. Can this be an issue?

EmployeeListComponent


import { Component, OnInit, ViewChild } from '@angular/core';
import { MatSort, MatTableDataSource } from '@angular/material';
import { AngularFirestore } from '@angular/fire/firestore';
import { EmployeeService } from '../shared/employee.service';
import { Employee } from '../shared/employee';

@Component({
  selector: 'app-employee-list',
  templateUrl: './employee-list.component.html',
  styleUrls: ['./employee-list.component.scss']
})
export class EmployeeListComponent implements OnInit {

  displayedColumns: string[] = ['fullname', 'position', 'mobile'];
  dataSource;

  @ViewChild(MatSort) sort: MatSort;

  constructor(private service: EmployeeService) { }

  ngOnInit() {
    this.service.getEmployees().subscribe(employees => {
      this.dataSource = new MatTableDataSource(employees);
      console.log(this.dataSource);
    });
    this.dataSource.sort = this.sort;

  }

}


EmployeeList HTML


<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">

  <!-- Position Column -->
  <ng-container matColumnDef="fullname">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Full Name </th>
    <td mat-cell *matCellDef="let element"> {{element.fullname}} </td>
  </ng-container>

  <!-- Name Column -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Position </th>
    <td mat-cell *matCellDef="let element"> {{element.position}} </td>
  </ng-container>

  <!-- Weight Column -->
  <ng-container matColumnDef="mobile">
    <th mat-header-cell *matHeaderCellDef mat-sort-header> Mobile </th>
    <td mat-cell *matCellDef="let element"> {{element.mobile}} </td>
  </ng-container>

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


EmployeeService


getEmployees() {
    return this.firestore.collection<Employee>('employees')
      .snapshotChanges()
      .pipe(
        map(actions => actions.map(a => {
          const data = a.payload.doc.data();
          const id = a.payload.doc.id;
          return { id, ...data } as Employee;
       })
        )
      );
}

Employee Class


export class Employee {
  id: string;
  fullname: string;
  empCode: string;
  position: string;
  mobile: string;
}



Solution

  • You are trying to access data from outside the subscription, it should be inside.

    After this you will run into trouble with this.sort being undefined in within the ngOnInit hook, this occurs because you are trying to access a HTMLelement that has not yet been draw. (MatSort)

    There is a lifecycle hook you can use to ensure that the view is ready to be accessed by the @ViewChild, this is ngAfterViewInit, only is only fired when the HTML is drawn.

    Try structuring like so.


    import { Component, AfterViewInit, ViewChild } from '@angular/core';
    import { MatSort, MatTableDataSource } from '@angular/material';
    import { AngularFirestore } from '@angular/fire/firestore';
    import { EmployeeService } from '../shared/employee.service';
    import { Employee } from '../shared/employee';
    
    @Component({
      selector: 'app-employee-list',
      templateUrl: './employee-list.component.html',
      styleUrls: ['./employee-list.component.scss']
    })
    export class EmployeeListComponent implements AfterViewInit{
    
      displayedColumns: string[] = ['fullname', 'position', 'mobile'];
      dataSource;
    
      @ViewChild(MatSort) sort: MatSort;
    
      constructor(private service: EmployeeService) { }
    
      ngAfterViewInit(): void
      {
        this.service.getEmployees().subscribe(employees => {
          this.dataSource = new MatTableDataSource(employees);
          if (this.sort) // check it is defined.
          {
              this.dataSource.sort = this.sort;
          }
        });
      }
    
    }
    

    Documentation AfterViewInit Lifecycle // More details.