如何解决@angular/material表格排序及过滤功能异常问题
问题:Angular Material表格排序与过滤功能异常
异常表现
- 排序问题:仅对当前分页数据做前端排序,切换页面后新页面数据仍在自身范围内排序,未请求服务器返回全量数据排序后的分页结果。
- 过滤问题:仅能搜索当前页存在的内容,无法匹配全量数据库中其他页的数据;清除过滤条件后,总条数变为默认页大小(5),而非数据库总条数。
现有代码
组件代码
import { MatPaginatorModule, MatPaginator } from '@angular/material/paginator'; import { MatTableModule } from '@angular/material/table'; import { MatSort, MatSortModule } from '@angular/material/sort'; import { CommonModule } from '@angular/common'; import { TroData } from './regulation-orders-table.model'; import { RegulationOrdersTableService } from './regulation-orders-table.service'; import { MatTableDataSource } from '@angular/material/table'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { EventEmitter, Output, ViewChild, inject } from '@angular/core'; @Component({ selector: 'app-traffic-regulation-orders', standalone: true, imports: [CommonModule, MatPaginatorModule, MatTableModule, MatSortModule, MatFormFieldModule, MatInputModule], templateUrl: './regulation-orders-table.component.html', styleUrls: ['./regulation-orders-table.component.css'] }) export class TrafficRegulationOrdersComponent { regulationOrdersTableService: RegulationOrdersTableService = inject(RegulationOrdersTableService); tableData: TroData[] = []; pageSize: number = 5; pageIndex: number = 0; totalRecords: number = 0; displayedColumns: string[] = ['externalId', 'expectedImpact', 'description', 'isPermanentChange', 'locationType', 'locationData']; @Output() selectedTroEmitter = new EventEmitter<string>(); dataSource: MatTableDataSource<TroData> = new MatTableDataSource<TroData>([]); @ViewChild(MatSort) sort!: MatSort; @ViewChild(MatPaginator) paginator!: MatPaginator; constructor() { this.fetchData(); } fetchData(): void { const currentPage = this.pageIndex + 1; console.log('Fetching data for page:', currentPage); this.regulationOrdersTableService.getTroList(currentPage, this.pageSize).then((response: { items: TroData[], totalCount: number }) => { this.tableData = response.items; this.totalRecords = response.totalCount; this.dataSource.data = this.tableData; this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; setTimeout(() => { this.paginator.pageIndex = this.pageIndex; this.paginator.length = this.totalRecords; }); }).catch((error) => { console.error('Error loading table data', error); }); } onRowClick(data: TroData): void { this.selectedTroEmitter.emit(data.externalId); } onPageChange(event: any): void { this.pageSize = event.pageSize; this.pageIndex = event.pageIndex; this.fetchData(); } applyFilter(event: Event): void { const filterValue = (event.target as HTMLInputElement).value; this.dataSource.filter = filterValue.trim().toLowerCase(); } }
HTML代码
<mat-form-field> <mat-label>Filter</mat-label> <input matInput (keyup)="applyFilter($event)" placeholder="Search" /> </mat-form-field> <table mat-table [dataSource]="dataSource" class="mat-elevation-z8" matSort> <ng-container matColumnDef="externalId"> <th mat-header-cell *matHeaderCellDef mat-sort-header>External ID</th> <td mat-cell *matCellDef="let row">{{ row.externalId }}</td> </ng-container> <ng-container matColumnDef="expectedImpact"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Expected Impact</th> <td mat-cell *matCellDef="let row">{{ row.expectedImpact }}</td> </ng-container> <ng-container matColumnDef="description"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Description</th> <td mat-cell *matCellDef="let row">{{ row.description }}</td> </ng-container> <ng-container matColumnDef="isPermanentChange"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Permanent Change</th> <td mat-cell *matCellDef="let row">{{ row.isPermanentChange }}</td> </ng-container> <ng-container matColumnDef="locationType"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Location Type</th> <td mat-cell *matCellDef="let row">{{ row.locationType }}</td> </ng-container> <ng-container matColumnDef="locationData"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Location Data</th> <td mat-cell *matCellDef="let row">{{ row.locationData }}</td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onRowClick(row)" class="clickable-row"></tr> </table> <mat-paginator [length]="totalRecords" [pageSize]="pageSize" [pageIndex]="pageIndex" [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons aria-label="Select page" (page)="onPageChange($event)"> </mat-paginator>
服务代码
import { Injectable } from '@angular/core'; import { TroData } from './regulation-orders-table.model'; @Injectable({ providedIn: 'root' }) export class RegulationOrdersTableService { async getTroList(currentPage: number, pageSize: number): Promise<{ items: TroData[], totalCount: number }> { const response = await fetch('/api/traffic-regulation-orders/list', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ page: currentPage, pageSize: pageSize, }), }); if (!response.ok) { throw new Error('Failed to fetch data'); } const data = await response.json(); return { items: data.items, totalCount: data.totalCount }; } async getTroById(externalId: string): Promise<TroData> { const data = await fetch(`/api/traffic-regulation-orders/load/${externalId}`); return await data.json(); } }
修复方案
排序问题修复
当前排序仅在前端对分页数据生效,需改为服务器端排序:
- 监听
MatSort的sortChange事件,捕获排序字段和方向 - 修改数据请求逻辑,将排序参数传递给后端
- 后端接收排序参数,对全量数据排序后再分页返回
过滤问题修复
当前过滤仅在前端对分页数据生效,需改为服务器端过滤:
- 维护过滤条件变量,过滤变化时触发服务器请求
- 修改数据请求逻辑,将过滤参数传递给后端
- 后端接收过滤参数,返回符合条件的数据及总条数
- 清除过滤时重置参数,重新请求全量数据的分页结果
修改后的代码
组件代码(关键修改)
import { ..., AfterViewInit } from '@angular/core'; export class TrafficRegulationOrdersComponent implements AfterViewInit { // 新增排序、过滤参数 sortColumn: string = ''; sortDirection: 'asc' | 'desc' | '' = ''; filterValue: string = ''; // 初始化后监听排序变化 ngAfterViewInit() { this.sort.sortChange.subscribe((sortState) => { this.sortColumn = sortState.active; this.sortDirection = sortState.direction; this.pageIndex = 0; // 排序后回到第一页 this.fetchData(); }); } // 修改fetchData,传递排序、过滤参数 fetchData(): void { const currentPage = this.pageIndex + 1; this.regulationOrdersTableService.getTroList(currentPage, this.pageSize, this.sortColumn, this.sortDirection, this.filterValue) .then((response: { items: TroData[], totalCount: number }) => { this.tableData = response.items; this.totalRecords = response.totalCount; this.dataSource.data = this.tableData; // 移除重复设置paginator和sort,避免多次初始化 setTimeout(() => { this.paginator.pageIndex = this.pageIndex; this.paginator.length = this.totalRecords; }); }).catch((error) => { console.error('Error loading table data', error); }); } // 修改applyFilter,触发服务器请求而非前端过滤 applyFilter(event: Event): void { this.filterValue = (event.target as HTMLInputElement).value.trim().toLowerCase(); this.pageIndex = 0; // 过滤后回到第一页 this.fetchData(); } }
服务代码(关键修改)
async getTroList( currentPage: number, pageSize: number, sortColumn?: string, sortDirection?: string, filterValue?: string ): Promise<{ items: TroData[], totalCount: number }> { const response = await fetch('/api/traffic-regulation-orders/list', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ page: currentPage, pageSize: pageSize, sortColumn, sortDirection, filter: filterValue }), }); if (!response.ok) { throw new Error('Failed to fetch data'); } const data = await response.json(); return { items: data.items, totalCount: data.totalCount }; }
后端调整说明
后端接口/api/traffic-regulation-orders/list需要新增处理逻辑:
- 接收
sortColumn、sortDirection参数,对全量数据执行排序 - 接收
filter参数,根据业务需求对数据执行过滤(如匹配指定字段) - 返回过滤、排序后的分页数据,以及对应的总条数(过滤后的总记录数)
内容的提问来源于stack exchange,提问作者Dinislam
相关产品推荐
相关产品推荐

