3

I am trying to add a matSort to this already working and existing MatTable. I've tried to implement what I found on this other thread here mat-sort not working on mat-table, as it looks like describing the same issue, but can't manage to make it work. I believe one of the reasons might be that my dataSource is asynchronously calculated in the template rather than in the TS file I reckon. Just for clarity, I had previously changed the way it was retrieving the dataSource, I moved it to the TS file, but still the UI wasn't updating.

Here a simplified version of the code:

TEMPLATE:

 <table matSort mat-table [dataSource]="(searchMode === 'shift') ? shifts : (searchMode === 'booking') ? bookings">

 <ng-container matColumnDef="orgName">
          <th mat-header-cell *matHeaderCellDef  mat-sort-header> Organisation </th>
          <td mat-cell *matCellDef="let element"> {{element.orgName}} </td>
        </ng-container>

        <ng-container matColumnDef="workerName">
          <th mat-header-cell *matHeaderCellDef  mat-sort-header> Worker </th>
          <td mat-cell *matCellDef="let element"> {{element.workerName}} </td>
        </ng-container>

</table>
      <mat-paginator [pageSizeOptions]="[5, 10, 20, 50, 100]" [pageSize]="20" [length]="total" (page)="onPageChange()" showFirstLastButtons></mat-paginator>

TS file:

import {MatTable, MatTableDataSource} from '@angular/material/table';
import {MatPaginator} from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';

export class TsGenerationSearchTableComponent implements OnInit, OnChanges, AfterViewInit {

  dataSource = new MatTableDataSource<Shift>();
  @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
  @ViewChild(MatSort, {static: false}) sort: MatSort;

  selectedWeekend: string;
  selectedWeekStart: string;


ngOnInit() {
    this.dataSource.paginator = this.paginator;
}

 ngAfterViewInit(): void {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }


  onPageChange() {
    const data: GetShiftTimesheetData = {
      action: (this.searchMode === 'shift') ? 'PageGetShiftsForTsAction' : (this.searchMode === 'booking') ? 'PageGetBookingsForTsAction',
      weekStartDate: this.selectedWeekStart,
      weekEndDate: this.selectedWeekend,
      paginator: {
        pageSize: this.paginator.pageSize,
        pageIndex: this.paginator.pageIndex
      }
    };
    this.search.emit(data);
  }

The sort does absolutely nothing visible in the UI. I've tried to subscribe in ngOnChanges to see if it was at least seen, like this:

ngOnChanges(changes: SimpleChanges): void {
if (this.dataSource && this.paginator && this.sort) {
      this.dataSource.paginator = this.paginator;
      this.dataSource.sort = this.sort;
    this.sort.sortChange.subscribe(val => {
      console.log(val);
    });
    }
  }

and the log shows that it's at least reacting to the sortChange:

{active: "workerName", direction: "asc"}

and

{active: "workerName", direction: "desc"}

but nothing in the UI. I've tried doing this.dataSource.sort = val; in the subscription but throws the error ERROR TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Any help/tip is welcome. Thanks a lot guys.

mike87
  • 309
  • 2
  • 11

2 Answers2

1

You could try programmatically creating the table element and rendering it only once data is available.

1) Create a separate component & template for table using mat-table.

2) Import that component and inject ComponentFactoryResolver class into your TsGenerationSearchTableComponent class.

import { ComponentFactoryResolver } from '@angular/core';
import { MyCustomMatTableComponent } from 'your/path/to/component';


export class TsGenerationSearchTableComponent implements OnInit {
    constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
    .... 

3) Use a placeholder via directive in your template for your incoming table. This placeholder will expose ViewContainerRef which is what you'll need there. ( Create then declare PlaceholderDirective in your app module. )

placeholder.directive.ts:

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({
  selector: '[appPlaceholder]'
 })
export class PlaceholderDirective {

  constructor(public viewContainerRef: ViewContainerRef) { }

}

tsGenerationSearchTableComponent.html:

<!-- Add this where ever you want your table to be rendered -->
<ng-template appPlaceholder></ng-template>

4) Add ViewChild selector for placeholder in tsGenerationSearchTableComponent.ts:

  @ViewChild(PlaceholderDirective) yourMatTableComponent: PlaceholderDirective; // By passing in 
  a class to ViewChild, it will find the first instance of said class.

5) Subscribe to dataSource fetch... and use ComponentFactoryResolver to render your table only when data is successfully retrieved.

this.fetchData.subscribe((data) => {
   const customMatTableFactory = this.componentFactoryResolver.resolveComponentFactory(MyCustomMatTableComponent); 
   const viewContainerRef = this.matTablePlaceHolder.viewContainerRef;

   hostViewContainerRef.clear(); // This will clear anything that was previously rendered in the ViewContainer.
   const componentRef = viewContainerRef.createComponent(customMatTableFactory);
})
Ares
  • 411
  • 8
  • 15
  • 1
    Hi man, thanks a lot for the time you took to answer my question. I did solve it over the weekend, but your solution will still be useful for someone else. Thanks a lot! – mike87 Dec 09 '19 at 10:19
  • 1
    Oh nice. I had a similar issue with MatSort before... so I'll book mark your solution as well. :) Also, if you don't mind up voting my solution I'd appreciate it! – Ares Dec 09 '19 at 15:19
1

I've solved this issue in the following way. Basically the dataSource was being passed straight from the store, hence the sorting was not happening regardless of my efforts in the TS file.

So first of all I changed the datasource like this: <table matSort mat-table [dataSource]="dataSource"> and, in the TS file I created a method initialiseDataSource() to initialise it, which is called in the ngOnInit().

  initialiseDataSource() {
    const dataSource = this.searchMode === 'timesheet' ? this.timesheets : this.shifts);
    this.dataSource = new MatTableDataSource<Shift | Timesheet>(dataSource);
    setTimeout(() => {
      this.dataSource.sort = this.sort;
    });
  }

It couldn't actually be simpler. Biggest takeaway from this issue is that the sorting needs to be applied to a local dataSource to happen, and in my case it wasn't.

mike87
  • 309
  • 2
  • 11