Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply multiple filters in Angular Material Data Table?

Hi have a Data table with 4 cols- Name, Age, Marks, School. Now I want to apply filter for each of the columns. The filter works fine individually. But I am not able to combine the results of filter - example I am not able to filter out the Name which has marks > 90 and from 'abc' School.

I tried something like this, but it did not work.

applyFilter(filterValue: string) {
    console.log("Filter is applied upon:",this.dataSource)
    let initialDataSource = this.dataSource;
    this.dataSource.filter = filterValue.trim().toLowerCase();
    this.filteredDataSource = this.dataSource.filteredData
    this.filterVal = this.dataSource.filter;
    if(this.filterVal.length != 0){
      this.dataSource = new MatTableDataSource(this.filteredDataSource);
      this.dataSource.filter = filterValue.trim().toLowerCase();
    }else{
      this.dataSource = initialDataSource;
      this.dataSource.filter = filterValue.trim().toLowerCase();
      this.filteredDataSource = this.dataSource.filteredData
    }
  }

The above function does the filtering, but when I clear out the filter value, the table data does not change. Also it does not change when I delete a character.

like image 667
Saif Ali Avatar asked Apr 19 '19 05:04

Saif Ali


3 Answers

Declare the TableFilter interface for convenience:

interface ITableFilter {
  column: string;
  value: any;
}

The filter predicate function is being called once per row(!) when filtering is applied: if the predicate returns true, the current row will be included in filtered results. So we'll iterate over our filters array and return false if there's a mismatch in one of the columns.

Replace your dataSource filterPredicate with the following custom one:

  setDataSource() {
    this.dataSource = new MatTableDataSource(this.yourData);
    this.dataSource.filterPredicate = this.customFilterPredicate;
  }

   
  customFilterPredicate(data: any, filters: ITableFilter[]): boolean {        
    for (let i = 0; i < filters.length; i++) {
      const fitsThisFilter = data[filters[i].column].includes(filters[i].value);
      if (!fitsThisFilter) {
        return false;
      }
    }
    return true;
  }

Afterwards you may apply your filter like that:

// implement some relevant logic to build the filters array
const filter: ITableFilter | any = [{column: 'name', value: 'foo'}, {column: 'description', value: 'bar'}];
// this is how you apply a filter
this.dataSource.filter = filter;

It's a little bit hackish that we need to set filters as 'any'; but the filter function expects a string, and I found no other way to get around it.

like image 55
grreeenn Avatar answered Sep 20 '22 20:09

grreeenn


Althoug the answer from grreeenn is perfectly fine, I wasn't able to combine multiple filter values with this approach. This is what I came up with:

The filters array would consist of multiple objects

const filters = [{key: name, filterValue: 'Mark'}, {key: score, filterValue: 1}]

To make the filterPredicate accept the list you can stringify the array with

JSON.stringify(filters)

I changed the filterPredicate method a little to work with multiple values at once

customFilterPredicate(data: any, filters: string): boolean {
  let match = true;
  const filtersList = JSON.parse(filters)
  filtersList.forEach(filterObj => {
    match = match && data[filterObj.key].toLocaleLowerCase().indexOf(filterObj.filterValue.toLocaleLowerCase()) !== -1;
  });
  return match;
};

So the filter would only return true if all values are matched, otherwise it returns false.

like image 24
JB17 Avatar answered Sep 18 '22 20:09

JB17


You can do so by replacing the filterPredicate function with your own filter function and passing multiple values (as an object) to the filter.

Example code:

this.timeSlotDataSource.filterPredicate = (data: any, filterObjString: string) => {
    const filterObj = JSON.parse(filterObjString);
    const filterValue1 = filterObj.filterValue1;
    const filterValue2 = filterObj.filterValue2;
    const filterValue3 = filterObj.filterValue3;
    let available = false;

    // You may add additional logic here if needed
    if('your own logic')
         available = true;
    else
         return false;

    return available && 
         ( data.slotName.toLowerCase().indexOf(filterValue1) !== -1 || 
           data.slotInfo.toLowerCase().indexOf(filterValue2) !== -1 ||
           data.slotTime.toLowerCase().indexOf(filterValue3) !== -1);
}

filter(){
    const filterObj = { 
        filterValue1: this.filterValue1,
        filterValue2: this.filterValue2,
        filterValue3: this.filterValue3,
    };
    this.timeSlotDataSource.filter = JSON.stringify(filterObj);
}
like image 23
luke Avatar answered Sep 20 '22 20:09

luke