Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Table] - Add example with multiple filtering columns #6178

Open
dev054 opened this issue Jul 31, 2017 · 52 comments

Comments

@dev054
Copy link

commented Jul 31, 2017

Bug, feature request, or proposal:

Proposal.

What is the expected behavior?

It would be good to see an example showing multiple filters (in this case inputs/selects) to filter rows/cells in md-table.

You can see this as an example.

What is the current behavior?

Currently the filter example just shows how to filter multiple columns by a single input.

@andrewseguin andrewseguin self-assigned this Jul 31, 2017

@andrewseguin andrewseguin added the P4 label Sep 5, 2017

@mikeaxle

This comment has been minimized.

Copy link

commented Sep 27, 2017

@andrewseguin I haven't come across any examples on how to accomplish this yet... any leads?

@willshowell

This comment has been minimized.

Copy link
Contributor

commented Sep 27, 2017

This shows two separate text filters, but the concept would be the same for selects, checkboxes, etc.

https://plnkr.co/edit/oQOYQgW0vCx8tfAgb1w9?p=preview

EDIT: and here's a version filtering with a slider

https://plnkr.co/edit/KZChr8jw1XJCCeNiDmUj?p=preview

@mikeaxle

This comment has been minimized.

Copy link

commented Sep 27, 2017

thanks! this has helped me sort out my issue, I couldn't tell from the sample code that it was just a filter in a filter, but the code in that plunker you sent is well formatted, I could easily understand it. Thanks again

@andrewseguin andrewseguin added this to Features in Table Oct 19, 2017

@andrewseguin andrewseguin moved this from Features to Docs in Table Oct 19, 2017

@andrewseguin andrewseguin removed the mat-table label Oct 23, 2017

@nickwinger

This comment has been minimized.

Copy link

commented Dec 21, 2017

Hi,

actually the mat(cdk)-table doesn't seem to be as flexible as promised.
I want to have a second header-row above the normal header-row
where i can have text-input fields for filtering each column
This doesn't seem to be possible as of right now ?

Regards,
Nick

@Spanja

This comment has been minimized.

Copy link

commented Dec 21, 2017

@nickwinger ,
try this, this is working for me :

<ng-container matColumnDef="entity">
  <mat-header-cell *matHeaderCellDef>
    <div fxFlexFill>
      <span translate="front.monitoring.quarterly.entity"></span>
      <mat-form-field floatPlaceholder="never">
        <mat-icon matPrefix>search</mat-icon>
        <input matInput [formControl]="entityFilter">
      </mat-form-field>
    </div>
  </mat-header-cell>
  <mat-cell *matCellDef="let history">{{ history.entity }}</mat-cell>
</ng-container>
@nickwinger

This comment has been minimized.

Copy link

commented Dec 21, 2017

@Spanja
i don't know what you are trying to tell me ?
Where is the "second" header row in here ??
This is only a filter header, actually i have the normal header with sorting also

@Spanja

This comment has been minimized.

Copy link

commented Dec 21, 2017

My bad I misunderstood, I thought you wanted a second row in the header instead of a second header.

@erickyi2006

This comment has been minimized.

Copy link

commented Jan 31, 2018

trying to implement multiple columns filtering using the filterPredicate. The limitation i saw when trying to perform filtering on multiple columns is that the "this" pointer is that of the MatTableDataSource and not that of my component. if the filterPredicate pass back the context of my component, then i can have a simpler solution. At this point, the work around is have a form based input to accept search values for each column or to have another table above the main mat-table.

@erickyi2006

This comment has been minimized.

Copy link

commented Feb 1, 2018

solve it! woot!!!
first, as i said earlier, there is a limitation in the filteredPredicate, i tried to put in the component "this" pointer into each data item which solved my problem of accessing the "this" pointer inside the filterPredicate function. but still trying to manipulate this.dataSource.filteredData is too buggy.

i ended up rolling my own filter function.
the issue itself deals with a 2nd header. I solved it by putting a footer instead as this is our requirement. implementing another top header is similar.

Sharing my poc. hope this is useful to others.

  <mat-table [dataSource]="dataSource" matSort>
    <ng-container matColumnDef="f1">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Field 1 </mat-header-cell>
      <mat-cell *matCellDef="let row"> {{row.f1}} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="f2">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Field 2</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{row.f2}}</mat-cell>
    </ng-container>

    <mat-header-row *matHeaderRowDef="displayedColumns" ></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;">
    </mat-row>
  </mat-table>

  <div class="eps-footer-row mat-header-row" role="row">
    <div class="eps-footer-cell mat-header-cell mat-column-f1" role="columnheader">
        <input (keyup)="applyColumnFilter('f1', $event.target.value)" placeholder="search Field 1" />
    </div>
    <div class="eps-footer-cell mat-header-cell mat-column-f2" role="columnheader">
        <input (keyup)="applyColumnFilter('f2', $event.target.value)" placeholder="search Field 2" />
    </div>
  </div>

  <mat-paginator [pageSizeOptions]="[5, 25, 50, 100]"></mat-paginator>


Updated: I also styled the two css elements; "eps-footer-row" and "eps-footer-cell". no magic here, just background colors and color

@nickwinger

This comment has been minimized.

Copy link

commented Feb 1, 2018

I also did it this way now as a workaround, but it is not perfect when it comes to horizontal scrolling and responsivness.
It is a hack and not a real second mat-header-row.

@erickyi2006

This comment has been minimized.

Copy link

commented Feb 6, 2018

interesting to see why u need the horizontal scrolling. what is your requirement? pls share a simplified view , maybe we can input some ideas

@nickwinger

This comment has been minimized.

Copy link

commented Feb 14, 2018

Why i need horizontal scrolling ?
If you want to display a lot of data columns, that just don't fit the browser width, what should happen ?
-> of course horizontal scrolling.
The mat-table should be flexible, it should of course support horizontal scrolling (like any data-table)
I have set minimum widths on different data-columns, so there appears a horizontal scrollbar...

@erickyi2006

This comment has been minimized.

Copy link

commented Feb 15, 2018

from a technical solution, yes, the horizontal scrolling would be do fine.
but .... my UX expert would strongly throw her eyes up :p
her usual analogy - u won't put more than 8 points in your powerpoint slides, so why would u put more than 8 columns.
no dispute on what u r trying to achieve. from a better UX, I agree with my UX expert. yr call

@nickwinger

This comment has been minimized.

Copy link

commented Feb 15, 2018

Lol, my UX expert says the same, but he agreed with me:

  1. The Table should be so flexibel
  2. some heavy Data applications just have to support horizontal scrolling, not everything has to look super cool pretty. Sometime data visualization is just technical...
    Think of fixing the first 2 or 3 columns and then just scroll right to compare data...
    My argument, why do we support vertical scrolling then ?
    Why do woman hate white socks ?

Cheers from a non UX expert ;-D

@erickyi2006

This comment has been minimized.

Copy link

commented Feb 18, 2018

first question: why do women hate white socks? hahahahha. i am curious. :p
btw, i do love your idea suggestion : "fixing the first 2 or 3 columns". I would love to see this feature implemented.

maybe a suggestion back to the developers of this group that we can do it via an option e.g.

        fixedColumns:   {
            leftColumns: [0,1] // fixed the first and 2nd column - zero index
            rightColumns: [0] // fixed the right hand 1st column
        }

very good suggestion. (Y)

@irowbin

This comment has been minimized.

Copy link

commented Feb 19, 2018

@erickyi2006 I am using mat-menu, mat-select, mat-input everywhere to meet my requirements for the mat-table like context menu, column filter etc;

For instance here is the gif:
video_001 1 1

@erickyi2006

This comment has been minimized.

Copy link

commented Feb 19, 2018

tnx @irowbin , good demo u got there. looks v configurable. well done. is there a question for me?

@astra1

This comment has been minimized.

Copy link

commented Mar 1, 2018

Wonderful, @irowbin! Could you provide some sort of example with mat-select in md-table' header?

@irowbin

This comment has been minimized.

Copy link

commented Mar 1, 2018

@astra1 here is some snippet from my code:

<!-- repeat this to all columns -->
 <ng-container cdkColumnDef="Column1">
         <mat-header-cell *cdkHeaderCellDef>
                 <!-- instead of adding mat-sort-header directive to the mat-header-cell, i've move it on span tag to separate sorting and filtering icon .-->
                <span mat-sort-header>Column1 </span>
                 <!-- i've applied css rules to the icon-->
                <span (click)="onFilterClick('col1')" class="filter-icon">
                    <mat-icon>keyboard_arrow_down</mat-icon>
                </span>
                <filter-template [openFilter]="true" *ngIf="templateFor === 'col1' "></filter-template>
         </mat-header-cell>
         <mat-cell *cdkCellDef="let row">{{row.Column1}}</mat-cell>
   </ng-container>
// mat-table component.ts
onFilterClick(templateFor:string){

  this.templateFor = templateFor;
}


// inside the filter-template component.ts 
@ViewChild('filterMenu') triggerMenu: MatMenuTrigger;
@Input() openFilter: boolean;

ngOnChanges(){

 if(this.openFilter) {
    this.triggerMenu.openMenu()
  }
}
<!-- filter-template component.html -->
<span #filterMenu="matMenuTrigger" [matMenuTriggerFor]="menu" id="filter-panel">

<mat-menu #menu="matMenu" [overlapTrigger]="false">
 <div (click)="$event.stopPropagation()" (keyup)="$event.stopPropagation()" (keydown)="$event.stopPropagation()">
   <form autocomplete="off"  (ngSubmit)="onFilterSubmit()" [formGroup]="filterForm">
			
    <mat-form-field  style="width: auto;">
	<mat-select formControlName="op" placeholder="Condition:">
		<mat-option *ngFor="let con of conditions" [value]="condi.id">
			 {{con.text}}
		</mat-option>
		</mat-select>
	</mat-form-field>
				
	<mat-form-field  *ngIf="!forFirstValue">
		<input formControlName="val1" matInput placeholder="Value">
	</mat-form-field>

	<mat-form-field class="font-sm" *ngIf="forSecondValue">
	   <input formControlName="val2" matInput placeholder="Second Value">
	</mat-form-field>
				
	<button [disabled]="hasNoValue" matRipple type="submit"></button>                
	</form>
  </div>
 </mat-menu>
</span>

I hope you'll get some clue from this snippet. 😆

@patrickschlaepfer

This comment has been minimized.

Copy link

commented Mar 8, 2018

thanks to @irowbin . I do miss something, get an ERROR TypeError: Cannot read property 'templateRef' of undefined.

@irowbin

This comment has been minimized.

Copy link

commented Mar 8, 2018

@patrickschlaepfer I think i have no idea of your exception! 🤣

@pjcoupe

This comment has been minimized.

Copy link

commented Mar 12, 2018

@irowbin I also get cannot read property of 'templateRef' of undefined. From what I can tell the html bit <filter-template [openFilter]="true" ... needs to have some sort of templateRef passed to it

@erickyi2006

This comment has been minimized.

Copy link

commented Mar 13, 2018

didn't try but i think typo is here
<filter-template [openFilter]="true" *ngIf="templateFor === 'col1">

missing single quote in col1

@Tronghoang

This comment has been minimized.

Copy link

commented Apr 8, 2018

@alexisdoualle it's work for me. Tks <3

@SushanRajShakya

This comment has been minimized.

Copy link

commented Apr 17, 2018

@alexisdoualle
Where are the values for data and filter in function createFilter( ), do they get auto assigned or do we have to pass some value to them ?

No proper documention for using filterPredicate in the Angular Material, so having a hard time understanding how it actually works

@alexisdoualle

This comment has been minimized.

Copy link

commented Apr 17, 2018

@drop039beats Yes they are auto assigned, you don't have to worry about them. By creating your own predicate function, you're overriding the default filter function, which only checks for the presence of string "filter" in the available data (MatTableDataSource). With a custom function you can specify which field you want to check (as in data.id), and with my workaround you can assign each input to every field in the table.

You can create a MatTableDataSource object simply like this:

      this.dataSource = new MatTableDataSource()
      this.dataSource.filterPredicate = this.createFilter();
      this.dataSource.data = yourData
@SushanRajShakya

This comment has been minimized.

Copy link

commented Apr 17, 2018

@alexisdoualle
Thanks a lot it worked like charm, we need to set filterPredicate before we set the dataSource.filter that is why it was not working for me. Better to put it in onNgInit( )

I had to make the filterPredicate dynamic based on the number of columns to be filtered

public createFilter(): (data: any, filter: any) => boolean {
    const filterFunction = function(data, filter): boolean {
      const searchData = JSON.parse(filter);
      let status = false;
      for (const key in searchData) {
        if (data[key].indexOf(searchData[key]) !== -1) {
          status = true;
        } else {
          status = false;
          break;
        }
      }
      return status;
    };
    return filterFunction;
  }
@srukali

This comment has been minimized.

Copy link

commented Apr 26, 2018

@alexisdoualle With your implementation are you able to search two terms within the same column? I did something similar with a custom predicate but I wasn't able to do something like search for 'John' and 'Bob' in the name filter at the same time.

@alexisdoualle

This comment has been minimized.

Copy link

commented Apr 26, 2018

@srukali Sure, if you want to have only one filter for names and be able to search multiple names separated by, say, a white space, you could do something like this:

createFilter() {
   let filterFunction = function(data: any, filter: string) : boolean {
     let searchTerms = JSON.parse(filter)
     let idSearch = data.id.toString().indexOf(searchTerms.id) != -1 
     let nameSearch = () => {
       let found = false;
       searchTerms.name.trim().toLowerCase().split(' ').forEach(word => {
         if (data.name.toLowerCase().indexOf(word) != -1) {found = true}
       });
       return found
     }
     return idSearch && nameSearch()
   }
   return filterFunction
 }
@NikolaiGarkusha

This comment has been minimized.

Copy link

commented Apr 27, 2018

@alexisdoualle, I use your createFilter function. If I have two arguments (like name and id), all works fine. But if I return third value, data is not displayed

@kal93

This comment has been minimized.

Copy link

commented Jun 17, 2018

@irowbin any chance we can get a stackblitz for the gif you shared?

@p97z

This comment has been minimized.

Copy link

commented Jun 19, 2018

stackblitz would be great!

@KMathisGit

This comment has been minimized.

Copy link

commented Jun 29, 2018

Building on-top of what @alexisdoualle has given for a per-column filtering solution 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

@jordanA29

This comment has been minimized.

Copy link

commented Jul 10, 2018

Is it possible to have it both ways? I mean filtering on the full table and per column? I don't see how to do it after modifying the filterPredicate. Anyone?

@kal93

This comment has been minimized.

Copy link

commented Aug 24, 2018

@alexisdoualle 's solution worked for me.But I still can't figure out a way to retain the default filter once filterPredicate is overriden.
StackBlitz
I'm for a way to filter on top of whatever data the column filters return.
Is this even possible after overriding filterPredicate

@alexisdoualle

This comment has been minimized.

Copy link

commented Aug 25, 2018

@kal93 @jordanA29 https://stackblitz.com/edit/angular-hbakxo-xctfqy?file=app/table-filtering-example.ts

This lets you have individual filters for each column and a top-level filter for all columns (which you can test with the value '999')

The trick was adding a property to the stringified object (.topFilter), which when set to true (by the top-level form control) will make the custom predicate function use the OR operator, so that every field is parsed, like in the default predicate.

  customFilterPredicate() {
    const myFilterPredicate = function(data:PeriodicElement, filter:string) :boolean {
      let searchString = JSON.parse(filter);
      let nameFound = data.name.toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1
      let positionFound = data.position.toString().trim().indexOf(searchString.position) !== -1
      if (searchString.topFilter) {
          return nameFound || positionFound 
      } else {
          return nameFound && positionFound 
      }
    }
    return myFilterPredicate;
  }

Of course, if you want all columns to be parsed (like 'weight' and 'symbol'), you need to add them to the code, either explicitly, or by looping through the 'data' parameter's properties.

@shemanifisher

This comment has been minimized.

Copy link

commented Sep 12, 2018

data.name.toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1

Hey Do you have any example how we can achieve same functionality of filter on each column using dynamic data? Thank you

@shemanifisher

This comment has been minimized.

Copy link

commented Sep 12, 2018

@kal93 @jordanA29 https://stackblitz.com/edit/angular-hbakxo-xctfqy?file=app/table-filtering-example.ts

This lets you have individual filters for each column and a top-level filter for all columns (which you can test with the value '999')

The trick was adding a property to the stringified object (.topFilter), which when set to true (by the top-level form control) will make the custom predicate function use the OR operator, so that every field is parsed, like in the default predicate.

  customFilterPredicate() {
    const myFilterPredicate = function(data:PeriodicElement, filter:string) :boolean {
      let searchString = JSON.parse(filter);
      let nameFound = data.name.toString().trim().toLowerCase().indexOf(searchString.name.toLowerCase()) !== -1
      let positionFound = data.position.toString().trim().indexOf(searchString.position) !== -1
      if (searchString.topFilter) {
          return nameFound || positionFound 
      } else {
          return nameFound && positionFound 
      }
    }
    return myFilterPredicate;
  }

Of course, if you want all columns to be parsed (like 'weight' and 'symbol'), you need to add them to the code, either explicitly, or by looping through the 'data' parameter's properties.

Hey Do you have any example how we can achieve same functionality of filter on each column using dynamic data? Thank you

@Rashid75

This comment has been minimized.

Copy link

commented Sep 28, 2018

@astra1 here is some snippet from my code:

<!-- repeat this to all columns -->
 <ng-container cdkColumnDef="Column1">
         <mat-header-cell *cdkHeaderCellDef>
                 <!-- instead of adding mat-sort-header directive to the mat-header-cell, i've move it on span tag to separate sorting and filtering icon .-->
                <span mat-sort-header>Column1 </span>
                 <!-- i've applied css rules to the icon-->
                <span (click)="onFilterClick('col1')" class="filter-icon">
                    <mat-icon>keyboard_arrow_down</mat-icon>
                </span>
                <filter-template [openFilter]="true" *ngIf="templateFor === 'col1' "></filter-template>
         </mat-header-cell>
         <mat-cell *cdkCellDef="let row">{{row.Column1}}</mat-cell>
   </ng-container>
// mat-table component.ts
onFilterClick(templateFor:string){

  this.templateFor = templateFor;
}


// inside the filter-template component.ts 
@ViewChild('filterMenu') triggerMenu: MatMenuTrigger;
@Input() openFilter: boolean;

ngOnChanges(){

 if(this.openFilter) {
    this.triggerMenu.openMenu()
  }
}
<!-- filter-template component.html -->
<span #filterMenu="matMenuTrigger" [matMenuTriggerFor]="menu" id="filter-panel">

<mat-menu #menu="matMenu" [overlapTrigger]="false">
 <div (click)="$event.stopPropagation()" (keyup)="$event.stopPropagation()" (keydown)="$event.stopPropagation()">
   <form autocomplete="off"  (ngSubmit)="onFilterSubmit()" [formGroup]="filterForm">
			
    <mat-form-field  style="width: auto;">
	<mat-select formControlName="op" placeholder="Condition:">
		<mat-option *ngFor="let con of conditions" [value]="condi.id">
			 {{con.text}}
		</mat-option>
		</mat-select>
	</mat-form-field>
				
	<mat-form-field  *ngIf="!forFirstValue">
		<input formControlName="val1" matInput placeholder="Value">
	</mat-form-field>

	<mat-form-field class="font-sm" *ngIf="forSecondValue">
	   <input formControlName="val2" matInput placeholder="Second Value">
	</mat-form-field>
				
	<button [disabled]="hasNoValue" matRipple type="submit"></button>                
	</form>
  </div>
 </mat-menu>
</span>

I hope you'll get some clue from this snippet. 😆

@irowbin i required this feature, i have copied whole code but when i select value from select , it prevent selecting value due to event.stopPropagation(). I did not get any help
Kindly help

@irowbin

This comment has been minimized.

Copy link

commented Sep 28, 2018

@Rashid75 That was to prevent closing the matMenu while firing other events inside the matMenu. Maybe you just forgot to wrap with form group to get changed values of form control?
e.g.

<div [formGroup]="filterForm"> <!-- i think you missed this line -->
    <input formControlName="filterValue" type="text" />
</div>
@Rashid75

This comment has been minimized.

Copy link

commented Oct 1, 2018

@Rashid75 That was to prevent closing the matMenu while firing other events inside the matMenu. Maybe you just forgot to wrap with form group to get changed values of form control?
e.g.

<div [formGroup]="filterForm"> <!-- i think you missed this block -->
    <input formControlName="filterValue" type="text" />
</div>

@irowbin I have wrapped it with formGroup but did not work, Any working code snippet ? I am stuck on it since last week

@irowbin

This comment has been minimized.

Copy link

commented Oct 2, 2018

@Rashid75 here is a working example of just to test the values:
https://stackblitz.com/edit/filter-columns?file=src%2Fapp%2Ffilter%2Ffilter-col.component.html
Click on the button to open matManu, change some values and then click to the filter. You'll see the form data. Sorry for the ugly code. 😄

@samuelkavin

This comment has been minimized.

Copy link

commented Oct 26, 2018

Here working example. I was trying implement angular material chip on filter text when user press enter. Now I'm having issue. Can anyone help on this?

*First filter working good as expected
*Second filter with material chip needs to be work same as filter 1
*Text turn to chip when press enter

@c0d3r1010

This comment has been minimized.

Copy link

commented Nov 7, 2018

@samuelkavin That stackblitz link is 404 not found.

@Mkahnke

This comment has been minimized.

Copy link

commented Nov 7, 2018

@irowbin would you mind sharing the code from the demo you posted? That is exactly what i need right now and im not getting it to work on my end.

@goodmite

This comment has been minimized.

Copy link

commented Nov 17, 2018

@samuelkavin can you post link again. Your stackblitz link isnt working.

@malbarmawi

This comment has been minimized.

Copy link

commented Feb 22, 2019

I have answered about this topic and create a repo explain how I did it Filtering different columns in a material table

image

source 🛠,live preview 🚀

@sbazinet

This comment has been minimized.

Copy link

commented Jun 21, 2019

I am trying to get this example to work with the code provided. However I'm getting an angular error on this line...

listings
error

Is this a problem because the control is in a submenu? If so can I go about fixing this?

@Exomus

This comment has been minimized.

Copy link

commented Jul 4, 2019

angular_table_filtered
Here is a link to the link to the stackblitz I used to develop my solution that you can browse in depth :
Angular Material Filtered Table

Explanation on the thinking process:

I developped a solution for this problem using Mat Menu and Dynamic component injection.
You will need a library named ng-dynamic-component to handle the inputs and the outputs of filters injected dynamically.

The main goal is to define the settings of the table through this structure type:

settingList = [
    { columnName: 'position', filterMode: FilterMode.NUMBER_MODE },
    { columnName: 'name', filterMode: FilterMode.TEXT_MODE },
    { columnName: 'weight', filterMode: FilterMode.NUMBER_MODE },
    { columnName: 'symbol', filterMode: FilterMode.TEXT_MODE },
    { columnName: 'date', filterMode: FilterMode.DATE_MODE },
  ];

It is the main reason why I inject my filter dynamically.

All filter follows this 'interface' to satisfy a common behaviour:

export class FilterImplementation {
  @Input() columnName: string;
  @Output() predicateEmitter = new EventEmitter<ColumnPredicate>();
}

The principle is to have a generic Mat Menu that returns a ColumnPredicate containing the name of the column to filter, and the predicate used to filter.

export class ColumnPredicate {
  name: string;
  predicate: (value:string) => boolean;
}

Next, you need to switch the filterPredicate based on the current filter triggered by the Mat Menu.
It's a bit hacky for filtering, due to the unflexible behaviour of the table. I trigger the filter by passing a value that is not used in the filterPredicate logic.

  applyFilter(columnPredicate: ColumnPredicate) {
    const newPredicate = (data: PeriodicElement, notUsedParameter: string) => {
      return columnPredicate.predicate.call(undefined, data[columnPredicate.name]);
    }
    this.dataSource.filterPredicate = TableFilteredComponent.composePredicateAnd(this.dataSource.filterPredicate, newPredicate);
    this.dataSource.filter = 'useless trigger'; // Triggering filter (It is a little bit of hacking but hey, angular material not so flexible after all)
  }

  static composePredicateAnd(materialPredicate: (data, value) => boolean, filterPredicate: (data, value) => boolean) {
    return (data, value) => materialPredicate.call(undefined, data, value) && filterPredicate.call(undefined, data, value);
  }

To sum up the flow of this filter table:
Trigger Mat Menu => Inject dynamically a filter component and giving it the name of the column => The filter returns a ColumnPredicate based on the information filled up => dataSource.filterPredicate is updated with the predicate and a fake value is sent to trigger the filter => Filtering is done

I would love to have a method on dataSource with the signature addFilter(column: string, predicate: (columnValue: string) => boolean) that would be for me the most natural way to define a column filter and avoid my hacky part.

Note that this is a prototype that I will continue to improve. Feel free to give your opinion about it and to point out any problem you have using it.

@andzejsw

This comment has been minimized.

Copy link

commented Jul 24, 2019

Improved @alexisdoualle by allowing unlimited count of provided object keys and values and no need to know them by names: createFilter() { let filterFunction = function(data, filter) : boolean { let searchTerms = JSON.parse(filter); for (var property in searchTerms) { if (searchTerms.hasOwnProperty(property)) { if(data[property].toString().indexOf(searchTerms[property]) == -1) return false; } } return true; } return filterFunction; }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
You can’t perform that action at this time.