Filtering specific column in Angular Material table in angular 5 [duplicate]
Asked Answered
P

9

28

In Angular material official website it is mentioned that filterPredicate: ((data: T, filter: string) => boolean) will filter data based on specific field. But don't getting how to start.

I have seen example but not getting:-https://stackblitz.com/edit/angular-material2-table?file=app%2Fapp.component.html

By default it filter based on whole object but i want to search only based on single property of json.

Pastel answered 16/1, 2018 at 7:54 Comment(0)
K
25

Working filters on each column, demo link Stackblitz.

To filter specific column in mat-table, add a search field for the column as below;

<mat-form-field class="filter" floatLabel="never">
    <mat-label>Search</mat-label>
    <input matInput [formControl]="nameFilter">
  </mat-form-field>

And we connect the inputs to FormControls from the ReactiveFormsModule.

    filterValues = {
    name: '',
    id: '',
    colour: '',
    pet: ''
 };

And we will watch the value of the filter inputs and modify this filter object and the data source’s filter property when they change. We must assign the stringified version of the filter object to the data source’s filter property

    ngOnInit() {
    this.nameFilter.valueChanges
      .subscribe(
        name => {
          this.filterValues.name = name;
          this.dataSource.filter = JSON.stringify(this.filterValues);
        }
      )
    this.idFilter.valueChanges
      .subscribe(
        id => {
          this.filterValues.id = id;
          this.dataSource.filter = JSON.stringify(this.filterValues);
        }
      )
    this.colourFilter.valueChanges
      .subscribe(
        colour => {
          this.filterValues.colour = colour;
          this.dataSource.filter = JSON.stringify(this.filterValues);
        }
      )
    this.petFilter.valueChanges
      .subscribe(
        pet => {
          this.filterValues.pet = pet;
          this.dataSource.filter = JSON.stringify(this.filterValues);
        }
      )
  }

We have to change the data source’s filterPredicate to tell it how to interpret the filter information.

    constructor() {
    this.dataSource.data = this.people;
    this.dataSource.filterPredicate = this.tableFilter();
    }

    tableFilter(): (data: any, filter: string) => boolean {
    let filterFunction = function(data, filter): boolean {
      let searchTerms = JSON.parse(filter);
      return data.name.toLowerCase().indexOf(searchTerms.name) !== -1
        && data.id.toString().toLowerCase().indexOf(searchTerms.id) !== -1
        && data.colour.toLowerCase().indexOf(searchTerms.colour) !== -1
        && data.pet.toLowerCase().indexOf(searchTerms.pet) !== -1;
    }
    return filterFunction;
}
 
Kirstiekirstin answered 14/2, 2019 at 4:55 Comment(0)
M
19

I managed to do like this:

this.dataSource.filterPredicate = (data, filter) =>
      (data.name.indexOf(filter) !== -1 ||
        data.id.indexOf(filter) !== -1 );
  }
Mehta answered 4/5, 2018 at 12:26 Comment(2)
#50265411Defiant
It may also be necessary to apply trim() and toLowerCase() on the filter input and value(s) to be matched. This will remove any whitespace and ignore any variations in capitalisation. this.dataSource.filterPredicate = (data, filter) => (data.name.trim().toLowerCase().indexOf(filter.trim().toLowerCase()) !== -1);Screenplay
F
8

Just declare a function with the following declaration in your component and then assign it to DataSource.filterPredicate. To use the filter just assign a string to DataSource.filter property.

  customFilter(Data: T, Filter: string): boolean {
    return <true if Data matches filter>
  }
Foretaste answered 23/1, 2018 at 11:17 Comment(6)
Thanks! it was really useful for me. But I still cant do it, this is what I did this.dataSource.filterPredicate = (data: userData, filter: string) => data.email.indexOf(filterValue) != -1; How can I filter now?Rotogravure
Change to this.dataSource.filterPredicate = (data: userData, filter: string) => data.email.indexOf(filter) != -1 and then apply the filter using this.dataSource.filter = filterValueForetaste
#50265411Defiant
@EduardoMauro Does there is a way to do something like this : customFilter(Data: T, Filter: string[]) : boolean { return boolean } ??Sheerness
@LoganWlv I think it is not possible. You want to use an array of strings in your customFilter, but dataSource property "filter" is a string. Check the documentation: material.angular.io/components/table/apiForetaste
@LoganWlv I made a filter that works with a comma-separated list. Doesn't fit in a comment though.Protonema
M
6

You can get to filter by a dynamic column, as in no hardcoded column name, doing the following:

// On input focus: setup filterPredicate to only filter by input column
setupFilter(column: string) {
  this.dataSource.filterPredicate = (d: TableDataSourceType, filter: string) => {
    const textToSearch = d[column] && d[column].toLowerCase() || '';
    return textToSearch.indexOf(filter) !== -1;
  };
}

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

In the template you can have something like this:

<ng-container matColumnDef="item-filter">
  <th mat-header-cell *matHeaderCellDef>
    <input (keyup)="applyFilter($event.target.value)" (focus)="setupFilter('name')" />
  </th>
</ng-container>

Or a more complex example, dynamically create a header row with per-column filtering:

<table mat-table [dataSource]="dataSource">
   <ng-container *ngFor="let filterCol of ['names', 'age', 'address']">
     <ng-container matColumnDef="filterCol">
       <th mat-header-cell *matHeaderCellDef>
         <input (keyup)="applyFilter($event.target.value)" (focus)="setupFilter(filterCol)"/>
       </th>
     </ng-container>
   </ng-container>

   <tr mat-header-row *matHeaderRowDef="['names', 'age', 'address']"></tr>
</table>

Be aware that you cannot have multiple header rows with the same keys, so this will not work:

<tr mat-header-row *matHeaderRowDef="['names', 'age', 'address']"></tr>
<tr mat-header-row *matHeaderRowDef="['names', 'age', 'address']"></tr>
Mandymandych answered 7/2, 2019 at 15:46 Comment(4)
It's working great. Is it possible to filter by several columns at once? Right now the result is always reset once you filter by another column. It would be awesome it you could like filter in column 1, then filter those results in column 5, filter those combined results by column 31 and so on. A dynamic combination filter so to speak.Essen
This is actually what I was looking for, thank you ! I guess it's good to add .toString().trim(). .. I had a issue where one of table data was a numberLesko
@Rui Marques, What is d: TableDataSourceType here in this example?Morbific
@phphunger it is the interface of whatever data you are inputing to the table. filterPredicate is part of the table api, so its inputs are what is mentioned in material.angular.io/components/table/apiMandymandych
C
2

You have to override the filterPredicate of your dataSource.

You want to specify what properties in your data the filter is applied to :-

this.dataSource.filterPredicate = function(data, filter: string): boolean {
    return data.name.toLowerCase().includes(data.symbol.toLowerCase().includes(filter) || data.position.toString().includes(filter);
};
Corporeity answered 23/10, 2019 at 4:9 Comment(0)
V
1

Use filterPredicate to override filter logic using customFilter()

Demo Link

Source Link

enter image description here

        ...
        ngOnInit() {
            this.getRemoteData();

            // Overrride default filter behaviour of Material Datatable
            this.dataSource.filterPredicate = this.createFilter();
        }
        ...

        // Custom filter method fot Angular Material Datatable
        createFilter() {
            let filterFunction = function (data: any, filter: string): boolean {
            let searchTerms = JSON.parse(filter);
            let isFilterSet = false;
            for (const col in searchTerms) {
                if (searchTerms[col].toString() !== '') {
                isFilterSet = true;
                } else {
                delete searchTerms[col];
                }
            }

            let nameSearch = () => {
                let found = false;
                if (isFilterSet) {
                for (const col in searchTerms) {
                    searchTerms[col].trim().toLowerCase().split(' ').forEach(word => {
                    if (data[col].toString().toLowerCase().indexOf(word) != -1 && isFilterSet) {
                        found = true
                    }
                    });
                }
                return found
                } else {
                return true;
                }
            }
            return nameSearch()
            }
            return filterFunction
        }
Vitelline answered 4/4, 2020 at 12:9 Comment(0)
B
1

This helped me :

https://www.freakyjolly.com/angular-material-table-custom-filter-using-select-box/#.YB4GBugzbIU

And If you want to have "and" condition over all select fields, then have an array - foundThisField[], instead of boolean found = true/false

Eg. foundThisField : number = []

then if field/column/property is found , do foundThisField.push(1) or else foundThisField.push(0)

and at end, return foundThisField.includes(0)?false:true

Brambling answered 16/2, 2021 at 20:27 Comment(0)
I
0

To expand on Naresh's answer, the following HTML snippet shows how to get both sorting and filtering in the same column and also the little "x" button to clear filter terms:

   <ng-container matColumnDef="description">
    <th mat-header-cell  *matHeaderCellDef mat-sort-header>
      <mat-form-field >
        <mat-label>Search Description</mat-label>
        <input matInput 
               [formControl]="descriptionFilter" 
               (click)="$event.stopPropagation()">
        <button mat-button 
                *ngIf="descriptionFilter.value" 
                matSuffix mat-icon-button 
                aria-label="Clear" 
                (click)="descriptionFilter.setValue('')">
          <mat-icon>close</mat-icon>
        </button>
      </mat-form-field>
    </th>

    <td mat-cell *matCellDef="let assessment">
      {{assessment?.description}} </td>
  </ng-container>
Ironist answered 10/1, 2020 at 14:53 Comment(2)
Why not use an <input type="search"> instead of an input and a button?Stickup
I don't see a close button for type="search", looks like it an angular material thing, see github.com/angular/components/issues/13315, I got the code for my clear button from the examples page of Angular Material Input, material.angular.io/components/input/examplesIronist
L
-1

If you are looking for a range predicate to filter out a range of values on one attribute:

applyRangePredicate() {
  this.datasource.filterPredicate = (data:
    {id: string}, filter: string) => {
      let searchlist = JSON.parse(filter);
      var found = false
      searchlist.forEach(item => {
        if (data.id.indexOf(item) !== -1)
          found = true 
      })
      return found
    }
  }

then assign a json string to stream filtered data to the UI

this.applyRangePredicate()    
this.datasource.filter = JSON.stringify(string[])
Liberal answered 20/5, 2019 at 15:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.