'Angular 7 Sorting on Custom DataSource

I've got a custom DataSource that I've created in my Risk component to reach out to an API, retrieve a list of Risks and display them in a table. The table displays properly however my Sorting isn't working.

I'm not entirely sure why however I call a refresh on the page every time the sort-able header is clicked, yet nothing. I've tried several different examples & configurations and I'm missing something.

Here's my risk.component.ts

export class RiskDashboardComponent implements AfterViewInit, OnInit {

@ViewChild(MatSort) sort: MatSort;

dataSource: RiskDataSource;

displayedColumns = ['riskName'];

constructor(private riskService: RiskManagmentService) {}

ngOnInit() {
    this.dataSource = new RiskDataSource(this.riskService);
    this.dataSource.loadRisks();
}

ngAfterViewInit() {
    merge(this.sort.sortChange).pipe(
        tap(() => this.loadRisksPage())
    )
        .subscribe();
}

loadRisksPage() {
    this.dataSource.loadRisks();
}


}

export class RiskDataSource implements DataSource<IRisk> {

private risksSubject = new BehaviorSubject<IRisk[]>([]);
private loadingSubject = new BehaviorSubject<boolean>(false);

constructor(private riskService: RiskManagmentService) {}

connect(collectionViewer: CollectionViewer): Observable<IRisk[]> {
    return this.risksSubject.asObservable();

}

disconnect(collectionViewer: CollectionViewer): void {
    this.risksSubject.complete();
    this.loadingSubject.complete();
}

loadRisks() {
    this.loadingSubject.next(true);
    this.riskService.getAllRisks().subscribe(risk => this.risksSubject.next(risk));
}
}

The risk.component.html

<div>
<mat-table class="lessons-table mat-elevation-z8" [dataSource]="dataSource"
           matSort matSortActive="riskName">
    <ng-container matColumnDef="riskName">
        <mat-header-cell *matHeaderCellDef mat-sort-header> Risk Name </mat-header-cell>
        <mat-cell *matCellDef="let risk"> {{risk.riskRegister.riskName}} </mat-cell>
    </ng-container>
    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</div>

(inb4 missing div tag, its in there, just isn't showing)

Every time the Risk Name order arrow is clicked it queries the database again for the list of risks

Any help would be greatly appreciated!



Solution 1:[1]

As you are using your own datasource most of the example in the angular material documentation website does not apply to you.

Firstly, you need to get the matsort from the view child and pass it into the datasource.

RiskDashBoardComponent

Add this

ViewChild(MatSort) sort: MatSort;
ngOnInit() {
    this.dataSource = new RiskDataSource(this.riskService, this.sort);
    this.dataSource.loadRisks();
}

You will require a little knowledge of observables here.

Now you need to return an observable that listen to two things by merging them. So when either the risksSubject gets a new data or mat sort is emitting new sort sequence you will handle the data always by getting the current value of the sort direction and the current value of the risksSubject.

You can see how to implement a custom sortData function from the angular material documentation site.

https://material.angular.io/components/sort/examples

RiskDataSource

connect(collectionViewer: CollectionViewer): Observable<IRisk[]> {
    const displayDataChanges = [
      this.risksSubject,
      this._sort.sortChange
    ];
    merge(...displayDataChanges).pipe(map(() => {
        return this.sortData(this.risksSubject.getValue());
    }));

}

loadRisks() {
    this.loadingSubject.next(true);
    this.riskService.getAllRisks().subscribe(risk => 
     this.risksSubject.next(risk));
}

**

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 chronolegend