typescript - Sortera tabellkolumner i Angular


Jag försöker göra mina tabellkolumner sorteringsbara. Jag hittade den här handledningen här: https://www.youtube.com/watch?v=UzRuerCoZ1E&t=715s

Med den informationen slutade jag upp med följande:

Ett rör som hanterar sorteringen

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;
      }
    
    }

Här är komponenten som hjälper mig att bygga mitt bord. Här definierar jag variabeln sortedColumn.

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();
  }
}

Här är min mallfil. Som du kan se har jag bifogat min sort-pipe till min loop, som spottar ut tabellraderna. Det är värt att notera att sättet jag renderar tabellen på skiljer sig från videon. Till exempel lagras hans data i en array, men min lagras på Firebase. Han renderar sin tabell dynamiskt, men min är fast till ett visst antal kolumner. Jag hårdkodar även rubrikerna, men han använde variabelnamnen från sin array för att generera tabellrubrikerna. Jag är inte säker på om dessa skillnader skulle kunna hindra saker från att fungera.

<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>

Jag fick följande fel nedan, men kunde åtgärda det genom att injicera följande kod i min pipe-fil: list=!!list ? list : [];

Nu finns det inga fel, men sorteringen fungerar inte som förväntat. När jag klickar på tabellrubriken händer ingenting. Hur kan jag fixa detta?

Svar #1

glöm pipen. sortering via pipe är dålig praxis, leder antingen till buggig kod eller dålig prestanda.

Använd observables istället.

först ändrar du dina mallhuvudknappar till att anropa en funktion, och se också till att du matar in de faktiska egenskapsnamnen du vill sortera efter, snarare än huvudets innehåll:

<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>

använd sedan din sorteringsfunktion och importera den till din komponent:

  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;
  }

fixa sedan upp din komponent:

// 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();
  }
}

slutligen, fixa din ngFor:

<tr *ngFor="let s of stocks$ | async">

på detta sätt förlitar du dig inte på magi eller förändringsdetektion. du utlöser din sortering när den behöver utlösas via observables

Relaterade artiklar