Angular Material表格编程触发排序异常问题求助
我开发了一个基于Angular Material框架的演示应用,用于对ScheduleDateTime类型的数据数组进行排序。问题在于,当通过代码this.sort.sort(({ id: 'date', start: 'asc' }) as MatSortable);触发排序时,结果不符合预期:本该按完整日期(DD-MM-yyyy格式)排序,但实际仅按日期的日(DD)部分排序。
app.component.ts代码
import { AfterViewInit, ChangeDetectorRef, Component, OnInit, ViewChild } from '@angular/core'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort, MatSortable, Sort } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import * as moment from 'moment'; import { timer } from 'rxjs'; export interface SortableDate { date: string } export class ScheduleDateTime { id!: string; date!: string; highlighted! : boolean; } const COLUMNS_SCHEMA = [ { key: "date", type: "text", label: "Date" }, ] @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit, AfterViewInit { @ViewChild('paginator') paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; title = 'TableTest'; displayedColumns: string[] = COLUMNS_SCHEMA.map((col) => col.key); columnsSchema: any = COLUMNS_SCHEMA; dataSource!: MatTableDataSource<ScheduleDateTime>; futureScheduleDate: ScheduleDateTime[] = []; constructor( private changeDetector: ChangeDetectorRef ) { } ngOnInit(): void { this.dataSource = new MatTableDataSource(); } ngAfterViewInit(): void { timer(0).subscribe(() => { this.futureScheduleDate = this.getTestData(); this.dataSource.data = this.futureScheduleDate; this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; // Initial sorting by date this.sort.sort(({ id: 'date', start: 'asc' }) as MatSortable); }) } sortData(sort: Sort) { AppComponent.sortMethod(this.futureScheduleDate, sort); this.dataSource.data = this.futureScheduleDate; this.changeDetector.detectChanges(); } getTestData(): ScheduleDateTime[] { var retVal = [ { id: '', date: "24-03-1961", highlighted: false }, { id: '', date: "25-03-1961", highlighted: false }, { id: '', date: "26-03-1961", highlighted: false }, { id: '', date: "27-03-1961", highlighted: false }, { id: '', date: "24-04-1961", highlighted: false }, { id: '', date: "25-04-1961", highlighted: false }, { id: '', date: "26-04-1961", highlighted: false }, { id: '', date: "27-04-1961", highlighted: false } ]; return retVal; } static sortMethod(dateSort: SortableDate[], sort: Sort) { if (!sort.active || sort.direction == '') { return; } console.log(sort); dateSort.sort((a, b) => { let isAsc = sort.direction == 'asc'; var date1 = moment(a.date, 'DD-MM-yyyy HH:mm').toDate().getTime(); var date2 = moment(b.date, 'DD-MM-yyyy HH:mm').toDate().getTime(); if (date1 < date2) { console.log("date1 is earlier than date2"); return isAsc ? -1 : 1; } else if (date1 > date2) { console.log("date1 is later than date2"); return isAsc ? 1 : -1;; } else { console.log("date1 and date2 are the same"); return 0; } }); } }
app.component.html代码
<mat-table #table [dataSource]="dataSource" matSort (matSortChange)="sortData($event)" style="min-width: 800px;"> <ng-container matColumnDef="date"> <mat-header-cell *matHeaderCellDef mat-sort-header> Date </mat-header-cell> <mat-cell *matCellDef="let schedule"> <input type="text" matInput [value]="schedule.date" readonly> </mat-cell> </ng-container> <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row> <mat-row *matRowDef="let row; columns: displayedColumns;let i = index;" [ngClass]="{hovered: row.hovered, highlighted: row.highlighted}" #tr (mouseover)="row.hovered = true" (mouseout)="row.hovered = false"></mat-row> </mat-table> <mat-paginator #paginator pageSize='10' [length]="futureScheduleDate.length" [pageSizeOptions]="[1, 30, 50, 100]" showFirstLastButtons> </mat-paginator>
原因分析
问题出在初始排序时,Angular Material的MatSort默认使用字符串排序,而非你自定义的sortMethod。当你调用this.sort.sort(...)时,这个操作会触发MatSort的默认排序逻辑,而不是你的sortData方法——只有点击表头的排序按钮时,才会触发(matSortChange)事件并调用sortData。
另外,你的moment解析格式也有问题:测试数据里的日期只有DD-MM-yyyy,但你用了DD-MM-yyyy HH:mm来解析,虽然moment会兼容,但最好保持格式一致。
修复步骤
1. 设置自定义排序访问器,替换默认逻辑
通过dataSource.sortingDataAccessor可以指定每个字段的排序依据,这样无论是初始调用排序还是点击表头排序,都会使用你的规则:
在ngAfterViewInit中添加以下代码:
this.dataSource.sortingDataAccessor = (item, property) => { if (property === 'date') { // 将DD-MM-yyyy格式的字符串转为时间戳,用于数值排序 return moment(item.date, 'DD-MM-yyyy').valueOf(); } return item[property as keyof ScheduleDateTime]; };
2. 简化代码(可选)
设置sortingDataAccessor后,MatTable会自动处理排序逻辑,你可以移除手动的sortData方法和模板中的(matSortChange)绑定:
- 删除组件中的
sortData方法 - 修改模板,移除
(matSortChange)="sortData($event)":
<mat-table #table [dataSource]="dataSource" matSort style="min-width: 800px;"> <!-- 其余内容不变 --> </mat-table>
3. 修正moment解析格式
将日期解析格式改为和测试数据一致的DD-MM-yyyy,避免不必要的兼容处理。
完整修改后的ngAfterViewInit示例
ngAfterViewInit(): void { timer(0).subscribe(() => { this.futureScheduleDate = this.getTestData(); this.dataSource.data = this.futureScheduleDate; this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; // 设置自定义排序访问器 this.dataSource.sortingDataAccessor = (item, property) => { if (property === 'date') { return moment(item.date, 'DD-MM-yyyy').valueOf(); } return item[property as keyof ScheduleDateTime]; }; // 初始排序 this.sort.sort(({ id: 'date', start: 'asc' }) as MatSortable); }) }
为什么之前只按日排序?
当MatSort默认处理字符串排序时,会逐字符比较日期字符串:比如"24-04-1961"和"25-03-1961",前两位字符24小于25,所以会把24-04-1961排在25-03-1961前面,完全忽略了后面的月份和年份,导致看起来只按日排序。
通过sortingDataAccessor将日期转为时间戳后,排序时会按照实际的日期大小比较,而非字符串顺序。
内容的提问来源于stack exchange,提问作者janci

