Angular Material - Filter Table by multiple filters and multiple filter values at the same time

av0*_*000 6 typescript material-design angular-material angular-material2 angular

I'm trying to be able to have two different filters for a table of objects. The first one is a normal text/input based filter and works as expected.

The second one I'm trying to get working is a row of checkboxes labeled "Level 1", "Level 2", etc. On checking a checkbox I want to filter by the column "level" all of the currently checked checkboxes. Ideally the user could filter the table by both the text and level selection

I've read about using filterPredicate and tried to use this as a template but I must be missing something.

Current code snippet:

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>
Run Code Online (Sandbox Code Playgroud)

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;
  }
Run Code Online (Sandbox Code Playgroud)

Update

So far I was able to make some progress thanks to Fabian Küng but am still not home free. To clarify I'm hoping to have the text filter be able to search through multiple columns ('castingTime', 'duration', etc), and have the checkboxes only filter by level.

As of right now when I click on a checkbox I get this error: 'Cannot read property 'toLowerCase' of undefined' that points to this line of code:

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

but when I console log out data I can see that it has a level property, so I'm not seeing where I'm going wrong.

Here are the relevant code snippets:

    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);
  }
Run Code Online (Sandbox Code Playgroud)

Update II

As requested here is an example of one of the table rows: (this is for a d&d table of spells)

{
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"
}
Run Code Online (Sandbox Code Playgroud)

Final Update

这就是我最终的结果,以防有人被卡住。感谢 Fabian 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;
  }
Run Code Online (Sandbox Code Playgroud)

Fab*_*üng 7

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

我使用了链接的 stackblitz 并对其进行了修改以使其与您的复选框一起使用,请在 此处查看

在模板中,你有你的复选框,就像你设置的一样:

<mat-checkbox (change)="updateFilter()" [(ngModel)]="level.active" *ngFor="let level of levelsToShow">{{level.name}}</mat-checkbox>
Run Code Online (Sandbox Code Playgroud)

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

export interface Level {
  active: boolean;
  name: string;
  level: number;
}
Run Code Online (Sandbox Code Playgroud)

和一些数据:

levelsToShow: Level[] = [
  { level: 1, active: false, name: 'Level 1' },
  { level: 2, active: false, name: 'Level 2' },
  { level: 3, active: false, name: 'Level 3' },
];
Run Code Online (Sandbox Code Playgroud)

而最重要的部分是在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));
Run Code Online (Sandbox Code Playgroud)

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

updateFilter() {
  this.dataSource.filter = JSON.stringify(this.filteredValues);
}
Run Code Online (Sandbox Code Playgroud)