I'm trying to make my table columns sortable. I found this tutorial here: https://www.youtube.com/watch?v=UzRuerCoZ1E&t=715s
Using that information, I ended up with the following:
A pipe that handles the sorting
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sort',
pure: true
})
export class TableSortPipe implements PipeTransform {
transform(list: any[], column:string): any[] {
let sortedArray = list.sort((a,b)=>{
if(a[column] > b[column]){
return 1;
}
if(a[column] < b[column]){
return -1;
}
return 0;
})
return sortedArray;
}
}
Here's the component that helps me build my table. Here I define the sortedColumn variable.
import { NavbarService } from './../navbar/navbar.service';
import { LiveUpdatesService } from './live-updates.service';
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-live-updates',
templateUrl: './live-updates.component.html',
styleUrls: ['./sass/live-updates.component.scss']
})
export class LiveUpdatesComponent implements OnInit{
stocks$: Observable<any[]>;
sortedColumn: string;
constructor(private updatesService: LiveUpdatesService, public nav: NavbarService) {
this.stocks$ = this.updatesService.getStocks();
}
ngOnInit() {
this.nav.show();
}
}
Here's my template file. As you can see, I've attached my sort
pipe to the my loop, spitting out the table rows. It's worth noting that the way I'm rendering the table differs from the video. For example, his data is stored in an array, but mine is stored on Firebase. He's rendering his table dynamically, but mine is fixed to a certain number of columns. I'm also hardcoding the headers, but he used the variable names from his array to generate the table headers. I'm not sure if these differences could be preventing things from working.
<section class="score-cards">
<app-score-cards></app-score-cards>
</section>
<section class="live-updates-wrapper">
<div class="table-wrapper">
<table class="stock-updates">
<thead>
<tr>
<th class="ticker-fixed">Ticker</th>
<th><a (click)="sortedColumn = $any($event.target).textContent">Ask Price</a></th>
<th><a (click)="sortedColumn = $any($event.target).textContent">Tax Value</a></th>
<th><a (click)="sortedColumn = $any($event.target).textContent">Est. Value</a></th>
<th><a (click)="sortedColumn = $any($event.target).textContent">Location</a></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let s of stocks$ | async | sort : sortedColumn">
<td class="ticker-fixed">
<a target="_blank" href="https://robinhood.com/stocks/{{ s.TICKER }}">{{ s.TICKER }}</a>
<span class="sp500">{{ s.sp500_flag }}S&P</span>
</td>
<td>{{ s.CLOSE }}</td>
<td>{{ s.tax_diff }}</td>
<td>{{ s.MarketCap }}</td>
<td>{{ s.Sector }}</td>
</tr>
</tbody>
</table>
</div>
</section>
I was getting the following error below, but was able to fix it injecting the following code in my pipe file: list = !!list ? list : [];
Now there are no errors, but the sorting is not working as expected. When I click on the table header, nothing happens. How can I fix this?
forget the pipe. sorting via pipe is bad practice, leads to either buggy code or bad performance.
Use observables instead.
first change your template header buttons to call a function, and also make sure you're feeding the actual property names you want to sort by, rather than the header content:
<th><a (click)="sortOn('CLOSE')">Ask Price</a></th>
<th><a (click)="sortOn('tax_diff')">Tax Value</a></th>
<th><a (click)="sortOn('MarketCap')">Est. Value</a></th>
<th><a (click)="sortOn('Sector')">Location</a></th>
then, pull out your sort function and import to your component:
export function sortByColumn(list: any[] | undefined, column:string, direction = 'desc'): any[] {
let sortedArray = (list || []).sort((a,b)=>{
if(a[column] > b[column]){
return (direction === 'desc') ? 1 : -1;
}
if(a[column] < b[column]){
return (direction === 'desc') ? -1 : 1;
}
return 0;
})
return sortedArray;
}
then fix up your component:
// rx imports
import { combineLatest, BehaviorSubject } from 'rxjs';
import { map, scan } from 'rxjs/operators';
...
export class LiveUpdatesComponent implements OnInit{
stocks$: Observable<any[]>;
// make this a behavior subject instead
sortedColumn$ = new BehaviorSubject<string>('');
// the scan operator will let you keep track of the sort direction
sortDirection$ = this.sortedColumn$.pipe(
scan<string, {col: string, dir: string}>((sort, val) => {
return sort.col === val
? { col: val, dir: sort.dir === 'desc' ? 'asc' : 'desc' }
: { col: val, dir: 'desc' }
}, {dir: 'desc', col: ''})
)
constructor(private updatesService: LiveUpdatesService, public nav: NavbarService) {
// combine observables, use map operator to sort
this.stocks$ = combineLatest(this.updatesService.getStocks(), this.sortDirection$).pipe(
map(([list, sort]) => !sort.col ? list : sortByColumn(list, sort.col, sort.dir))
);
}
// add this function to trigger subject
sortOn(column: string) {
this.sortedColumn$.next(column);
}
ngOnInit() {
this.nav.show();
}
}
finally, fix your ngFor
:
<tr *ngFor="let s of stocks$ | async">
this way, you're not relying on magic or change detection. you're triggering your sort when it needs to trigger via observables
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With