在Angular中对表格列进行排序

6
我试图让我的表格列可排序。我在这里找到了这个教程:https://www.youtube.com/watch?v=UzRuerCoZ1E&t=715s 使用该信息,我最终得到了以下内容:
处理排序的管道
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;
      }
    
    }

这里是帮助我构建表格的组件。在这里,我定义了 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();
  }
}

这是我的模板文件。如您所见,我已将sort管道附加到循环中,以便输出表格行。值得注意的是,我呈现表格的方式与视频中的不同。例如,他的数据存储在数组中,但我的数据存储在Firebase上。他动态渲染表格,但我的表格固定为一定数量的列。我也硬编码了标题,但他使用变量名从数组生成表头。我不确定这些差异是否会导致问题无法解决。

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

我遇到了以下错误,但注入以下代码后成功解决:list = !!list ? list : []; 现在没有错误了,但是排序不像预期那样工作。单击表头时,什么都不会发生。我该如何解决?
图片链接:enter image description here

@Z. Bagley 我不知道我是否在使用一个。教程没有涉及到这个。对于我的无知,我感到抱歉。我只知道一点点Angular。 - Kellen
这个问题已经被解决了:https://dev59.com/k1kS5IYBdhLWcg3wCSdx#40463405 总体思路是:你需要在declarations中声明管道,然后还需要在providers中提供你的管道。如果你的唯一模块是AppModule,你可以在那里完成这两件事。 - Z. Bagley
@Z.Bagley 啊,是的!我使用了 ng g pipe 命令,所以所有内容都自动添加到了我的 app.module.ts 文件中。它已经被添加到了 declarations 中,但没有添加到 providers 中。providers 是必要的吗?我已经成功地实现了其他管道,而这些管道并没有被添加到 providers 中。 - Kellen
1
在提供程序中不需要它,因为你没有在组件中使用它。我认为我看到了实际的问题。在管道的第一行之前添加:list = !!list ? list : []; 问题是你在某个时候传入了 undefined,并且试图将 .sort(..) 应用于 undefinedlist = !!list ? list : [] 将会在任何时候将一个空数组应用于值,如果它是未定义的! - Z. Bagley
@Z.Bagley 太棒了,这修复了错误!不幸的是,我的排序仍然无法正常工作。当我点击标题时,没有任何反应。我可以为此创建一个单独的工单。 - Kellen
显示剩余3条评论
3个回答

11

不要使用管道。通过管道进行排序是不好的实践,会导致buggy代码或者性能不佳。

改用观察者。

首先将模板头部按钮更改为调用函数,并确保你正在提供实际要排序的属性名称,而不是标题内容:

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

然后,提取出你的排序函数并将其导入到组件中:

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

然后修复您的组件:

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

最后,修复你的ngFor

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

这样,您不需要依赖于魔法或更改检测。而是通过可观察对象在需要触发排序时进行触发。


你会把排序函数放在一个服务里吗? - Kellen
1
我觉得你不需要费力了,因为它没有依赖项和状态。这是一个很好作为“utlities/array.functions.ts”文件的候选项......或者你可以将lodash添加到你的项目中并使用它们的“sortBy”函数。 - bryan60
ERROR TypeError: Cannot read property 'list' of undefined at sortByColumn (array.functions.ts:11) at MapSubscriber.project (live-updates.component.ts:22) at MapSubscriber._next (map.js:29) at MapSubscriber.next (Subscriber.js:49) at CombineLatestSubscriber.notifyNext (combineLatest.js:73) at InnerSubscriber._next (InnerSubscriber.js:11) at InnerSubscriber.next (Subscriber.js:49) at BehaviorSubject.next (Subject.js:39) at BehaviorSubject.next (BehaviorSubject.js:30) at LiveUpdatesComponent.sortOn (live-updates.component.ts:26) - Kellen
由于某种原因,排序函数被提供了undefined而不是数组...你的getStocks()方法是否因为某种原因发出了undefined?无论如何,我修改了排序函数以期望并处理未定义的情况。 - bryan60
让我们在聊天中继续这个讨论 - Kellen
显示剩余2条评论

2
我认为您的值没有传递到管道中: 请尝试以下代码:
<tr *ngFor="let s of ((stocks$ | async) | sort : sortedColumn)">

1
你可以交换管道,而不是你的自定义管道接收一个数组,你可以接收你返回的可观察对象并在其上进行过滤,然后异步管道它。 - Ondie
我不太确定如何完成这个任务。我有点 Angular 新手。尽管如此,数据似乎正在流入管道中。当我在我的管道中运行 console.log(column); 和 console.log(list); 时,我可以看到数据。但是我的表格顺序没有更新。我在想是否是我渲染表格的方式有问题。我的表格与教程中使用的表格存在一些差异。 - Kellen

1
在给this.stocks$赋值之前,异步调用会在这里进行,表格将会加载并调用管道。
 constructor(private updatesService: LiveUpdatesService, public nav: NavbarService) {
    this.stocks$ = this.updatesService.getStocks();
  }

模板

 <tbody *ngIf="stocks$">
       <tr *ngFor="let s of stocks$ | sort : sortedColumn">
          ....
        </tr>
  </tbody>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接