Angular13 MatTable多列过滤输入值后返回空数组无控制台报错
问题根因
过滤后表格清空且无控制台报错,是三个逻辑bug叠加导致:
- MatTableDataSource 内置filter预处理逻辑会自动将传入的
dataSource.filter字符串做trim().toLowerCase()转换后再传入自定义过滤函数,现有代码的匹配逻辑没有统一做大小写、空格归一化,很容易出现大小写不匹配导致校验失败。 - 列过滤值未做空值兜底:输入框清空、FormControl初始化未传默认值时,返回的过滤值为
null/undefined,直接参与匹配时,indexOf(null)会将null转为字符串"null"做匹配,所有数据行都会返回匹配失败。 - 全局搜索值单独存在组件实例上,没有和列过滤值统一序列化传入过滤逻辑,容易出现闭包值不同步;同时存在重复订阅列过滤值变更的问题,部分列的匹配逻辑遗漏空值跳过判断,只要任意一个字段校验不通过,整行就会被判定为不匹配。
修复步骤
- 所有字段匹配逻辑统一做
trim().toLowerCase()归一化处理,空过滤值直接跳过该列校验,不阻断整体匹配 - 初始化所有列的过滤默认值为空字符串,所有输入值存入过滤配置前统一兜底为字符串类型,避免null/undefined参与匹配
- 移除重复的valueChanges订阅,将全局搜索值和列过滤值合并到同一个序列化对象中传入filter,避免值不同步
- 等dataSource初始化完成后再赋值filterPredicate、paginator、sort等配置,避免配置不生效
修复后代码参考
TS 部分
import { Component, ViewChild, OnInit } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { MatPaginator } from '@angular/material/paginator'; import { MatSort } from '@angular/material/sort'; import { FormControl } from '@angular/forms'; import { HttpClient } from '@angular/common/http'; import { LiveAnnouncer } from '@angular/cdk/a11y'; interface Domaine { name: string; description: string; // 其他表字段自行补充 } @Component({ selector: 'app-table-demo', templateUrl: './table-demo.component.html' }) export class TableDemoComponent implements OnInit { @ViewChild(MatPaginator) paginator!: MatPaginator; @ViewChild(MatSort) sort!: MatSort; dataSource!: MatTableDataSource<Domaine>; // 所有列过滤默认值初始化为空字符串 filterValues: Record<string, string> = { name: '', description: '' // 其余过滤列同理补全 }; globalFilter = ''; // 列过滤控件初始化时传空字符串默认值 nameFilter = new FormControl(''); descriptionFilter = new FormControl(''); // 其余列控件同理 constructor( private _httpClient: HttpClient, private _liveAnnouncer: LiveAnnouncer ) {} ngOnInit() { // 替换为实际的数据获取逻辑 this._httpClient.get<Domaine[]>('your-api-url').subscribe(res => { this.dataSource = new MatTableDataSource(res); this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; // dataSource初始化完成后再绑定过滤逻辑 this.dataSource.filterPredicate = this.customFilterPredicate; this.initFilterSubscribes(); }); } // 统一订阅列过滤事件,不要重复订阅 initFilterSubscribes() { this.nameFilter.valueChanges.subscribe(val => { this.filterValues.name = val ?? ''; this.triggerFilter(); }); this.descriptionFilter.valueChanges.subscribe(val => { this.filterValues.description = val ?? ''; this.triggerFilter(); }); // 其余列订阅同理 } // 全局搜索触发 applyFilter(globalVal: string) { this.globalFilter = globalVal ?? ''; this.triggerFilter(); } // 统一过滤触发逻辑 triggerFilter() { const filterParams = { ...this.filterValues, global: this.globalFilter }; this.dataSource.filter = JSON.stringify(filterParams); if (this.dataSource.paginator) { this.dataSource.paginator.firstPage(); } } // 自定义过滤逻辑 customFilterPredicate = (data: Domaine, filter: string): boolean => { const searchParams = JSON.parse(filter); const globalSearch = searchParams.global?.trim().toLowerCase() ?? ''; // 全局搜索校验 let isGlobalMatch = true; if (globalSearch) { // 全局搜索覆盖的字段自行增减 const searchContent = `${data.name} ${data.description}`.trim().toLowerCase(); isGlobalMatch = searchContent.includes(globalSearch); } if (!isGlobalMatch) return false; // 逐列校验,空值直接跳过 for (const column of Object.keys(this.filterValues)) { const columnFilterVal = searchParams[column]?.trim().toLowerCase() ?? ''; if (!columnFilterVal) continue; const columnContent = data[column as keyof Domaine]?.toString().trim().toLowerCase() ?? ''; if (!columnContent.includes(columnFilterVal)) { return false; } } return true; } announceSortChange(sortState: any) { if (sortState.direction) { this._liveAnnouncer.announce(`Sorted ${sortState.direction}ending`); } else { this._liveAnnouncer.announce('Sorting cleared'); } } }
HTML 部分
<mat-form-field> <input matInput [ngModel]="globalFilter" (ngModelChange)="applyFilter($event)" placeholder="全局搜索"/> </mat-form-field> <mat-paginator *ngIf="dataSource" #paginator [pageSize]="10" [pageSizeOptions]="[10, 50]"> </mat-paginator> <table mat-table *ngIf="dataSource" [dataSource]="dataSource" class="mat-elevation-z8" matSort (matSortChange)="announceSortChange($event)"> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by name"> Name <div> <mat-form-field> <input matInput class="form-field" [formControl]="nameFilter" placeholder="名称过滤"/> </mat-form-field> </div> </th> <td mat-cell *matCellDef="let row">{{row.name}}</td> </ng-container> <ng-container matColumnDef="description"> <th mat-header-cell *matHeaderCellDef mat-sort-header sortActionDescription="Sort by description"> Description <div> <mat-form-field> <input matInput class="form-field" [formControl]="descriptionFilter" placeholder="描述过滤"/> </mat-form-field> </div> </th> <td mat-cell *matCellDef="let row">{{row.description}}</td> </ng-container> <!-- 其余列定义同理补充 --> <tr mat-header-row *matHeaderRowDef="['name', 'description']"></tr> <tr mat-row *matRowDef="let row; columns: ['name', 'description'];"></tr> </table>
内容的提问来源于stack exchange,提问作者kiki keke
相关产品推荐
相关产品推荐

