Components: [MatTable] How to filter each column of the table

Created on 10 Jan 2018  路  14Comments  路  Source: angular/components

Bug, feature request, or proposal:

Is there a simple solution proposed by MatTable to filter each column independently ?

What is the expected behavior?

I want to search in my table by using one filter by column.

What is the current behavior?

I know it exists a system to filter the table with only one input which searches in all the columns of the table.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Angular 5.0.1
Material 5.0.1

Most helpful comment

EDIT : I rewrite it much better !!

I have made a filterPredicate who filter on multiples columns.

    this.dataSource.filterPredicate =
      (data: UserData, filters: string) => {
        const matchFilter = [];
        const filterArray = filters.split(',');
        const columns = [data.name, data.race, data.color];
        // Or if you don't want to specify specifics columns =>
        // const columns = (<any>Object).values(data);

        //Main loop
        filterArray.forEach(filter => {
          const customFilter = [];
          columns.forEach(column => customFilter.push(column.toLowerCase().includes(filter)));
          matchFilter.push(customFilter.some(Boolean)); // OR
        });
        return matchFilter.every(Boolean); // AND
      }

Let's say i have 4 animals in my datable, 2 dogs (one black, one white) AND 2 cats (one black, one white).

When i type "cat", only my two lines with cats stays.
Now, when i type "black" only the black cat line stay.

I hope this can be helpful for someone !

All 14 comments

Please keep GitHub issues for bug reports / feature requests. Better avenues for troubleshooting / questions are stack overflow, gitter, mailing list, etc.

@ancornelle did you find any answer?

Hello,

As you can see, joseph closed my feature request without analyzing...
I tried some development. For example, I tried to override the MatSortHeader component in order to add a filter directly on the header (as mat-header-cell is a directive) by using the template of this component which is on Github.
It doesn't work, I think there is something in the table generation which forbids this kind of cutomisation (Maybe only mat-sort-header tag is recognized..)
I think I won't used MatTable of Material2 for my project because of the reduced functionnalities which are proposed.

comibe the terms into string

onChange() { this.datasource.filter = this.column1 + '$' + this.column2; }
and you can use custom filter by specifing filterPredicate function like this

this.datasource.filterPredicate = (data, filter) => { const filterArray = filter.split('$'); return (!filterArray[0] || data.column1.toLowerCase().indexOf(filterArray[0].trim().toLowerCase()) > -1) && (!filterArray[1] || data.column2.indexOf(filterArray[1]) > -1) ; };

the trick is to pass multiple column filters as one string and spliting it to array later ... it would have been nice if we could pass filter as object or array .. i will open an issue for this.

Hello,

came across the same issue, after trying out @Abenezer solution, I tried upgrading it using JSON.
Seems to work just fine :

onChange(filterValues: Object) { this.dataSource.filter = JSON.stringify(filterValues); }

And the predicate can go like this :

this.dataSource.filterPredicate = (data, filter) => { const filterArray = JSON.parse(filter); return (!filterArray['last_name'] || data.last_name.indexOf(filterArray['last_name']) > -1) && (!filterArray['first_name'] || data.first_name.indexOf(filterArray['first_name']) > -1) ; };

I think you can improve it even further by adding a better comparaison function in the predicate or if your datasource row is of the same type/structure as you filtering object (which is what I went for).

EDIT : I rewrite it much better !!

I have made a filterPredicate who filter on multiples columns.

    this.dataSource.filterPredicate =
      (data: UserData, filters: string) => {
        const matchFilter = [];
        const filterArray = filters.split(',');
        const columns = [data.name, data.race, data.color];
        // Or if you don't want to specify specifics columns =>
        // const columns = (<any>Object).values(data);

        //Main loop
        filterArray.forEach(filter => {
          const customFilter = [];
          columns.forEach(column => customFilter.push(column.toLowerCase().includes(filter)));
          matchFilter.push(customFilter.some(Boolean)); // OR
        });
        return matchFilter.every(Boolean); // AND
      }

Let's say i have 4 animals in my datable, 2 dogs (one black, one white) AND 2 cats (one black, one white).

When i type "cat", only my two lines with cats stays.
Now, when i type "black" only the black cat line stay.

I hope this can be helpful for someone !

Below is my contribution : mix of @Dioude and @WizardPC solutions, but with dynamic columns notion !

(N.B. : Note that 'this.filters' refers to an array of objects bound to user input =>
{ id: [column id], value: [value to search for in said column] }

applyFilters() {
    const tableFilters = [];
    this.filters.forEach((filter) => {
      tableFilters.push({
        id: filter.columnId,
        value: filter.value
      });
    });
    this.appListData.filter = JSON.stringify(tableFilters);
  }

And initialisation:

ngOnInit() {
this.dataSource.filterPredicate = (data: any, filtersJson: string) => {
      const matchFilter = [];
      const filters = JSON.parse(filtersJson);

      filters.forEach(filter => {
        // check for null values!
        const val = data[filter.id] === null ? '' : data[filter.id];
        matchFilter.push(val.toLowerCase().includes(filter.value.toLowerCase()));
      });

       // Choose one
        return matchFilter.every(Boolean); // AND condition
        // return matchFilter.some(Boolean); // OR condition
    }
}

@StevePires, @Dioude and @WizardPC can you reproduce this on stackblitz please? This doesn't seem to work on mine.

filters

the filter array:

this.filters = [{product: null, category: null, subcategory: null, status: null, discount: false}];

The Apply Filter function:

applyFilter() { const tableFilters = []; this.filters.forEach((filter) => { tableFilters.push({ product: filter.product, category: filter.category, subcategory: filter.subcategory, status: filter.status, discount: filter.discount }); }); this.productsMDS.filter = JSON.stringify(tableFilters); }

Filter predicate Initialization

`this.productsMDS.filterPredicate = (data: any, filtersJson: string) => {

  const matchFilter = [];
  const filters = JSON.parse(filtersJson);
  const columns = (<any>Object).values(data);

  filters.forEach(filter => {

    // check for null values!
    /*const product = data[filter.product] === null ? '' : data[filter.product];
    const category = data[filter.category] === null ? '' : data[filter.category];
    const subcategory = data[filter.subcategory] === null ? '' : data[filter.subcategory];
    const status = data[filter.status] === null ? '' : data[filter.status];
    const discount = data[filter.discount] === null ? '' : data[filter.discount];
    matchFilter.push(product.toLowerCase().includes(filter.value.toLowerCase()));*/

    const customFilter = [];
    columns.forEach(column => customFilter.push(column.toLowerCase().includes(filter)));
    matchFilter.push(customFilter.some(Boolean)); // OR
  });

  // Choose one
  return matchFilter.every(Boolean); // AND condition
  // return matchFilter.some(Boolean); // OR condition
};`

Building on-top of what @alexisdoualle has given for a per-column filtering solution (#6178) I would like to add in how I managed to fit my filter inputs inside of my table column headers instead of else-where on the page. Also not have them trigger column sorting when they are focused for input.

I'd like to preface with saying this is just a proof of concept and small snippet, I hope it helps though!

<ng-container *ngFor="let column of displayedColumns" matColumnDef="{{column}}">
  <mat-header-cell *matHeaderCellDef>
    <span mat-sort-header>{{column | uppercase}}</span>
    <input class="filter-input" matInput (keyup)="applyFilter(column, $event.target.value)" placeholder="Filter {{column}}" />
  </mat-header-cell>
  <mat-cell *matCellDef="let row"> {{row[column]}}</mat-cell>
</ng-container>

If you are NOT using mat-sort functionality then the trick is simple, just include your filter input inside of the mat-header-cell.
If you are using mat-sort functionality then you will need to instead put your column heading text inside of a span and give that span the mat-sort-header class instead of the mat-header-cell.

Sample result:
image

Does any one know how to add filter based on date range?

Building on-top of what @alexisdoualle has given for a per-column filtering solution (#6178) I would like to add in how I managed to fit my filter inputs inside of my table column headers instead of else-where on the page. Also not have them trigger column sorting when they are focused for input.

I'd like to preface with saying this is just a proof of concept and small snippet, I hope it helps though!

<ng-container *ngFor="let column of displayedColumns" matColumnDef="{{column}}">
  <mat-header-cell *matHeaderCellDef>
    <span mat-sort-header>{{column | uppercase}}</span>
    <input class="filter-input" matInput (keyup)="applyFilter(column, $event.target.value)" placeholder="Filter {{column}}" />
  </mat-header-cell>
  <mat-cell *matCellDef="let row"> {{row[column]}}</mat-cell>
</ng-container>

If you are NOT using mat-sort functionality then the trick is simple, just include your filter input inside of the mat-header-cell.
If you are using mat-sort functionality then you will need to instead put your column heading text inside of a span and give that span the mat-sort-header class instead of the mat-header-cell.

Sample result:
image

Could be able to show applyFilter(column, $event.target.value) function how are you filtering on each column? Thank you

Hello everyone!
I would like to share my idea, because I had been trying to find a nice solution, but I couldn't. I am developing a CRUD application, so I use server side pagination, short and filter. The trick that I use is based on the ng-container directive, which can be used to inject another row into the table header. I created a new directive with two different filter type, like text input or select, but other types can be implemented furthermore.
Sample code: https://stackblitz.com/edit/angular-table-filter

My 2 cents based on methodology from the official docs:

Using applyFilter method in your Component:

public applyFilter(filterValue: string, column: SoundColumn) {

  this._filter = {
    ...this._filter,
    [column]: filterValue
  };

  if (!filterValue) delete this._filter[column];

  this.dataSource.filter = JSON.stringify(this._filter);
}

Then filterPredicate:

this.dataSource.filterPredicate = (data: DataType, filters: string) => {

  const parsedFilters: FilterType = JSON.parse(filters);

  return Object.keys(parsedFilters)
    .map(column => data[column].includes(parsedFilters[column]))
    .reduce((acc: boolean, curr: boolean) => (acc = curr) && acc, true);
}

Use in your template like so:

<input matInput
  type="text"
  placeholder="Column X Filter"
  (keyup)="applyFilter($event.target.value, 'column-x')">

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings