You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular13 MatTable多列过滤输入值后返回空数组无控制台报错

问题根因

过滤后表格清空且无控制台报错,是三个逻辑bug叠加导致:

  • MatTableDataSource 内置filter预处理逻辑会自动将传入的dataSource.filter字符串做trim().toLowerCase()转换后再传入自定义过滤函数,现有代码的匹配逻辑没有统一做大小写、空格归一化,很容易出现大小写不匹配导致校验失败。
  • 列过滤值未做空值兜底:输入框清空、FormControl初始化未传默认值时,返回的过滤值为null/undefined,直接参与匹配时,indexOf(null)会将null转为字符串"null"做匹配,所有数据行都会返回匹配失败。
  • 全局搜索值单独存在组件实例上,没有和列过滤值统一序列化传入过滤逻辑,容易出现闭包值不同步;同时存在重复订阅列过滤值变更的问题,部分列的匹配逻辑遗漏空值跳过判断,只要任意一个字段校验不通过,整行就会被判定为不匹配。
修复步骤
  1. 所有字段匹配逻辑统一做trim().toLowerCase()归一化处理,空过滤值直接跳过该列校验,不阻断整体匹配
  2. 初始化所有列的过滤默认值为空字符串,所有输入值存入过滤配置前统一兜底为字符串类型,避免null/undefined参与匹配
  3. 移除重复的valueChanges订阅,将全局搜索值和列过滤值合并到同一个序列化对象中传入filter,避免值不同步
  4. 等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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:18:27