top of page

Sorting Tables in Angular with RxJS



First let’s get the latest Angular CLI:

npm install -g @angular/cli

Next we want to create a simple project to test this out in using the ng CLI command:

ng new table-sorter-app

After that cd into your project and create a new component:

ng generate component table

We will be doing all of our work in the table.component.[ts|html] files. For the most part I will be talking mostly about the TypeScript side of things. I assume you can make your own table so will only show a basic snippet of the HTML code.


Let’s take a look at what’s table.component.ts will look like

import {Component, OnInit} from '@angular/core';
import {BehaviorSubject, combineLatest, Observable, of} from 'rxjs';
import {map, scan} from 'rxjs/operators';

type Row = {
    tableColOne: string;
    tableColTwo: string;
};

type Table = Array<Row>;

interface SortConfig{
    column: string;
    direction: 'asc'|'desc';
}

@Component({
    selector: 'app-table',
    templateUrl: './table.component.html',
    styleUrls: ['./table.component.styl'],
})
export class TableComponent implements OnInit{
    table=[];
    table$: Observable<Table>;
    sortableColumn$ = new BehaviorSubject<string>('');
    sortConfig={} as SortConfig;
    
    sortDirection$=this.sortableColumn$.pipe(
        scan<string,SortConfig>(
            (config,column)=>{
                return config.column===column          
                    ? { column,direction: config.direction==='desc' ? 
                                                    'asc' : 'desc'}          
                    : { column,direction: 'desc'};
            },
            {
            column: '',direction: 'desc'}
        )
    );
    
    constructor(){
        //This can be a more meaningful call to some table data
        this.table$=of(this.table);
    }
    
    ngOnInit(): void{
    this.table$=combineLatest([
        of(this.table),
        this.sortDirection$,
    ]).pipe(
        map(([data,sortConfig])=>{
            return sortConfig.column ? this.sort(data,sortConfig.column,sortConfig.direction) : data;
        })
    );
    this.sortDirection$.subscribe((config)=>{
        this.sortConfig=config;
        });
    }
    
    setSortColumn(column: string){
        this.sortableColumn$.next(column);
    }
    
    sort(
        data: Table=[],
        column: SortConfig['column'],
        direction: SortConfig['direction']='desc'
    ): Table["table"]{
        return data.sort((rowOne: Row,rowTwo: Row)=>{
            const firstValue=rowOne[column];
            const secondValue=rowTwo[column];
            const isDescending=direction==='desc';
            // values are equal so return 0 -- don't need to sort
            if(firstValue===secondValue) return 0;
            
            // toggle between descending and ascending dependent on the currently applied sort direction
            return (firstValue>secondValue) ? (isDescending ? 1 : -1) : 
                                                (isDescending ? -1 : 1)
        });
    }
}

Breaking this down we will be using BehaviorSubject, combineLatest, Observable, and of from rxjs as well as map and scan from rxjs/operators


We set our table$ variable as an Observable so that we can easily update our HTML if the table data changes. This is good for when you have some data coming from a live feed or just some random API.

We use BehaviorSubject as an event emitter to update the sorting of the table whenever the sortableColumn$ observable changes value.


sortDirections$ is another observable that pipes sortableColumn$ and uses scan as a reducer to return the correct sortConfig whenever sortableColumn$ changes.

In the constructor we just need to set table$ to something in order to stop TypeScript from complaining. You can do this in a more meaningful way, but for the sake of this tutorial it works just fine.

In the ngOnInit method combineLatest is used to combine the table data and sortDirection$ observables. We wrap table in of to convert it to an observable sequence. This way whenever either values changes it will update the table$ observable as mentioned before. We use combineLatest.pipe() to apply the sort function to the table data effectively sorting it on each update. Finally we subscribe to the sortDirections$ observable and update our sortConfig whenever the value changes. This last part is optional but you’ll see why this is important for my assignment later on.

Following that we have a setSortColumn function that updates the sortableColumn$ observable whenever we select a column to sort by (you’ll see this in the HTML snippet) and our sort function which is used to determine which order the table should be sorted in based on the selected column and previous sort order.

Moving on to our table.component.html file we can make use of our table$ observable and the setSortColumn function like so:

<table>
    <thead>
        <tr>
            <th>
                <a(click)="setSortColumn('tableColOne')">          
                    Table Column One
                </a>
            </th>
            <th>
                <a(click)="setSortColumn('tableColTwo')">          
                    Table Column Two
                </a>
            </th>
        </tr>
    </thead>
    <tbody>
        <ng-container*ngFor="let row of table$ | async">
            <tr>
                <td>          
                    {{ row.colOneData }}
                </td>
                <td>                            
                    {{ row.colTwoData }}
                </td>
            </tr>
        </ng-container>
    </tbody>
</table>

Here we can use the (click) event binding to call setSortColumn whenever a table heading is clicked. This will pass the column to sort on into the sortDirection$ observable and toggle between descending and ascending order. To generate the rows, we use *ngFor to loop through the data in the table$ observable and use the async pipe to subscribe to the table$ to ensure we get the latest data.


When all is said and done, you’ll have something that works like this:


I didn’t show this in the HTML above but in my assignment I’m using the sortConfig value in my table to switch between icons. Whenever the sortConfig.column matches the currently selected column to sort on I switch from the default icon to one of the ascending or descending icons. If you select a different column to sort on then it will revert the previously selected columns icon to the default.


An added benefit to sorting this way is that when you use observables you can connect live data and your table will be updated automagically!



Source: Medium - Dewaun Ayers


The Tech Platform

0 comments

Comments


bottom of page