Angular Material - 通过多个过滤器和多个过滤器值同时过滤表

Posted

技术标签:

【中文标题】Angular Material - 通过多个过滤器和多个过滤器值同时过滤表【英文标题】:Angular Material - Filter Table by multiple filters and multiple filter values at the same time 【发布时间】:2019-06-19 21:52:00 【问题描述】:

我试图为一个对象表设置两个不同的过滤器。第一个是基于普通文本/输入的过滤器,按预期工作。

我要工作的第二个是一排标记为“1 级”、“2 级”等的复选框。在选中一个复选框时,我想按“级别”列过滤所有当前选中的复选框复选框。理想情况下,用户可以通过文本和级别选择来过滤表格

我已阅读有关使用 filterPredicate 并尝试使用 this as a template 但我一定遗漏了一些东西。

当前代码sn-p:

html

//Input for text filter
<mat-form-field >
  <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter...">
</mat-form-field>

...

//Checkboxes for level filter
<div fxLayout="row" fxLayoutAlign="space-between center" class="margin-1">
    <mat-checkbox (change)="customFilterPredicate()" [(ngModel)]="level.active" *ngFor="let level of levelsToShow">level.level</mat-checkbox>
</div>

TS

ngOnInit() 
     this.dataSource.filterPredicate = this.customFilterPredicate();
  

...

applyFilter(filterValue: string) 
    this.dataSource.filter = filterValue.trim().toLowerCase();
  

customFilterPredicate() 
    const myFilterPredicate = (data): boolean => 
      return this.levelsToShow.some(data['level'].toString().trim());
    ;
    return myFilterPredicate;
  

更新

到目前为止,多亏了 F*** Küng,我才能取得一些进展,但我仍然没有回家。为了澄清,我希望文本过滤器能够搜索多个列(“castingTime”、“duration”等),并且复选框只能按级别过滤。

截至目前,当我点击一个复选框时,我收到此错误: '无法读取未定义的属性'toLowerCase'' 指向这行代码:

return data['level'].toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1 &&

但是当我控制台注销数据时,我可以看到它具有 level 属性,所以我看不到哪里出错了。

以下是相关代码sn-ps:

    HTML:
    
        <input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter..." [formControl]="textFilter">
        
        <mat-checkbox (change)="updateFilter()" [(ngModel)]="level.active" *ngFor="let level of levelsToShow">level.name</mat-checkbox>
    
    TS:
    
        levelsToShow: Level[] = [
             level: '1', active: false, name: 'Level 1' ,
             level: '2', active: false, name: 'Level 2' ,
             level: '3', active: false, name: 'Level 3' ,
             level: '4', active: false, name: 'Level 4' ,
             level: '5', active: false, name: 'Level 5' ,
             level: '6', active: false, name: 'Level 6' ,
             level: '7', active: false, name: 'Level 7' ,
             level: '8', active: false, name: 'Level 8' ,
             level: '9', active: false, name: 'Level 9' 
          ];
        
          levelFilter = new FormControl();
          textFilter = new FormControl();
          globalFilter = '';
        
          filteredValues = 
            level: '', 
            text: '', 
          ;

ngOnInit() 
    ...

    this.textFilter.valueChanges.subscribe((textFilterValue) => 
      this.filteredValues['text'] = textFilterValue;
      this.dataSource.filter = JSON.stringify(this.filteredValues);
    );

    this.dataSource.filterPredicate = this.customFilterPredicate();
  

  customFilterPredicate() 
    const myFilterPredicate = (data: Spell, filter: string): boolean => 
      var globalMatch = !this.globalFilter;

      if (this.globalFilter) 
        // search all text fields
        globalMatch = data['spellName'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['level'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['castingTime'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['distance'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['details'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['duration'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['school'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['effect'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1;
      

      if (!globalMatch) 
        return;
      

      let searchString = JSON.parse(filter);

      return data['level'].toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1 &&
        (this.levelsToShow.filter(level => !level.active).length === this.levelsToShow.length ||
          this.levelsToShow.filter(level => level.active).some(level => level.level === data.level.toString()));
    
    return myFilterPredicate;
  

  updateFilter() 
    this.dataSource.filter = JSON.stringify(this.filteredValues);
  

更新二

这里的要求是其中一个表格行的示例:(这是一个 d&d 法术表)


castingTime: "1 half action"
distance: "Short range attack"
duration: "1 round per Casting Level"
effect: "Once per round, you may take a half action to launch an arrow of acid from your palm, generating a new Spellcasting result to see if you hit.Each arrow inflicts 1d6 acid damage.↵  "
index: 0
level: "4"
school: "Creation"
spellName: "ACID ARROW"

最终更新

这是我最终的结果,以防有人被卡住。感谢 F*** Küng 解决了这一切!唯一需要注意的是我最终使用了文本输入“textFilter”的实际值,因为将文本过滤值字符串化然后解析它们(据我所知,过滤器只接受字符串)不断给我“JSON错误,意外值 0" 消息,据我所知,这将正常工作,除了现在我需要弄清楚如何限制过滤器。

customFilterPredicate() 
    const myFilterPredicate = (data: Spell, filter: string): boolean => 
      var globalMatch = !this.globalFilter;

      if (this.globalFilter) 
        // search all text fields
        globalMatch = data['spellName'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['level'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['castingTime'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['distance'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['details'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['duration'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['school'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1 ||
                      data['effect'].toString().trim().toLowerCase().indexOf(this.globalFilter.toLowerCase()) !== -1;
      

      if (!globalMatch) 
        return;
      

      return data.spellName.toString().trim().toLowerCase().indexOf(this.textFilter.value) !== -1 &&
        (this.levelsToShow.filter(level => !level.active).length === this.levelsToShow.length ||
          this.levelsToShow.filter(level => level.active).some(level => level.level === data.level.toString()));
    
    return myFilterPredicate;
  

【问题讨论】:

你能显示你试图显示/过滤的数据吗?例如。属性和实际的数据行。 是的,我现在就添加它。谢谢! 这一行 data['level'].toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1 &amp;&amp; 应该改为使用 searchString.text 而不是 name,因为您实际上从未设置过 filteredValuesname 属性(除非缺少该代码)。 【参考方案1】:

您在设置复选框然后使用复选框模型在customFilterPredicate 函数中进行过滤时处于正确的轨道上。

我使用了链接的 stackblitz 并对其进行了修改以使其适用于您的 复选框,检查它 here.

在模板中,您可以设置复选框:

<mat-checkbox (change)="updateFilter()" [(ngModel)]="level.active" *ngFor="let level of levelsToShow">level.name</mat-checkbox>

在组件中我添加了Level接口:

export interface Level 
  active: boolean;
  name: string;
  level: number;

还有一些数据:

levelsToShow: Level[] = [
   level: 1, active: false, name: 'Level 1' ,
   level: 2, active: false, name: 'Level 2' ,
   level: 3, active: false, name: 'Level 3' ,
];

最重要的部分是customFilterPredicate 函数。它的作用是,它在第​​一个条件中过滤name 属性,在第二个条件中它检查是否所有级别都设置为false,在这种情况下,我们返回true,因为我假设您想查看所有级别如果没有选中复选框。如果一个级别处于活动状态,我们会按该级别进行过滤,如果选择了多个级别,我们会按多个级别进行过滤。

return data.name.toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1 &&
        (this.levelsToShow.filter(level => !level.active).length === this.levelsToShow.length ||
         this.levelsToShow.filter(level => level.active).some(level => level.level === data.level));

一个小而重要的部分是在您选中/取消选中复选框时实际触发过滤。这可以通过再次设置数据源filter 来完成,并在复选框值更改时调用:

updateFilter() 
  this.dataSource.filter = JSON.stringify(this.filteredValues);

【讨论】:

非常感谢您的帮助。所以我想我几乎拥有它。为了澄清我希望让文本过滤器过滤多个列。当我尝试使用您发布的闪电战作为指南时,但在“返回数据['level'].toString().trim( ).toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1 &&" 我将使用相关代码更新我的问题。【参考方案2】:

如果您使用@angular/material,您可以使用mat-table-filter 来满足您复杂的过滤要求。

检查这个answer

【讨论】:

【参考方案3】:

更简单的方法

在数据源中使用FilterPredicate设置多个表达式

使用表单组分配将发送 filterValue 的控件

<form [formGroup]="filterForm">

  <mat-form-field>
    <input matInput placeholder="Model" formControlName="model" />
  </mat-form-field>

  <mat-form-field>
    <input matInput placeholder="Type" formControlName="modelType" />
  </mat-form-field>

  <mat-form-field appearance="fill">
    <mat-label>Status</mat-label>
    <mat-select formControlName="status">
      <mat-option value=""></mat-option>
      <mat-option value="Active">Active</mat-option>
      <mat-option value="InActive">InActive</mat-option>
    </mat-select>
  </mat-form-field>

#TypeScript 指定 filterPredicate 表达式

// Filter Predicate Expression to perform Multiple KeyWord Search
    this.dataSource.filterPredicate = ((data: DeviceInventory, filter) =>           
      const a = !filter.model || data.modelCode.trim().toLowerCase().includes(filter.model);
      const b = !filter.modelType || data.modelType.trim().toLowerCase().includes(filter.modelType);
      return a && b;
    ) as (DeviceInventory, string) => boolean;

订阅 FomrGroup 值更改以收听 filterValues

this.filterForm.valueChanges.subscribe(value => 
  this.dataSource.filter = value;
  console.log(value);
);

【讨论】:

以上是关于Angular Material - 通过多个过滤器和多个过滤器值同时过滤表的主要内容,如果未能解决你的问题,请参考以下文章

在角度 5 中过滤 Angular Material 表中的特定列 [重复]

填充数组并使其可使用 Angular Material 过滤

Angular Material Datepicker:如何过滤以便只允许数组中的日期?

如何过滤mat-tree组件Angular Material 6.0.1

如何将给定的指令值应用于 Angular Material 中的多个标签?

如何更改 Angular Material2 中主调色板的字体颜色?