Angular generic table multiple columns filtering custom filterPredicate

110 views Asked by At

I want to create generic table that takes tableColumns and dataSource as @Input(). I would like to be able to add custom fitlering by each table column. For the moment I have managed to initialize table FormGroup and to get FormGroup value on which I want to filter but further from here I really don't know how to proceede. I have tried to implement pipe and look at the other simillar solutions on internet but they all assume I know what are my tableColums (that I don't know as I am creating them dynamically.

Here is my code

    <table mat-table [dataSource]="dataSource">
  <ng-container *ngFor="let col of tableColumns; let i = index" [matColumnDef]="col.key">
    <ng-container>
      <th class="header" mat-header-cell *matHeaderCellDef>
        <span *ngIf="!col.config">
          {{ col['display'] }}
        </span>
        <form [formGroup]="form">
          <mat-form-field *ngIf="!col.config" class="filter">
            <input matInput placeholder="Filter" formControlName="{{tableColumns[i].key}}">
          </mat-form-field>
        </form>

      </th>
      <td mat-cell *matCellDef="let element; let row"> 
        <ng-container>
          {{ element[col.key] }}
        </ng-container>   
      </td>
    </ng-container>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="keys"></tr>
  <tr mat-row *matRowDef="let row; columns: keys"></tr>
</table>

and then in my ts file I have following:

  @Input() tableColumns: GenericTableColumn[] = []
  @Input() dataSource: Array<object> = []

  ngOnInit(): void {
    this.initFormControls()
    this.registerFormChangeListener()
  }

  initFormControls() {
    const formGroup: FormGroup = new FormGroup({})
    this.tableColumns.forEach((column) => {
      if (column.key !== 'actions') {
        let control: FormControl = new FormControl('')
        formGroup.addControl(column.key, control)
      }
    })
    this.form = formGroup
  }

my idea was to create another function that will transform my input of dataSource into MatTableDataSource and apply custom filter predicate. Something like this:

  registerFormChangeListener(){
    const tableDataSource = new MatTableDataSource(this.dataSource)
    tableDataSource.filterPredicate((tableDataSource, filter) => {
         // here I need to filter by object key value
        })
      }



my dataSource = {
{
    "id": "1",
    "name": "someValue1",
    "someOtherKey": "someOtherValue"

},
{
    "id": "2",
    "name": "someValue2",
    "someOtherKey": "someOtherValue2"
},
{
    "id": "3",
    "name": "someValue3",
    "someOtherKey": "someOtherValue2"
}
}

my filter object that is actually value of my form is: 

{
    "id": "",
    "name": "someValue1",
    "someOtherKey": "someOtherValue2"
}

and I am hoping to get my result as:

const fitleredResults = [
{
    "id": "1",
    "name": "someValue1",
    "someOtherKey": "someOtherValue"

},
{
    "id": "2",
    "name": "someValue2",
    "someOtherKey": "someOtherValue2"
}
]

Thanks for your help!

1

There are 1 answers

4
Eliseo On

You need understand how a customFilter function work

  1. A MatTableDataSource has a property: filter that it's a string
  2. A customFilter function is a function with received as argument, each element of the array an this "string" and you return true or false

When we have severals field to filter you can use JSON.stringify to generate the "string" and JSON.parse to recover the object

Perhafs this SO can sirve as inspiration

Tip: To iterate over all the keys of one object use Object.Keys or Object.Entries

Update

initFormControls() {
    const formGroup: FormGroup = new FormGroup({})
    ...
    this.form = formGroup
    //we subscribe to valueChanges
    this.form.valueChanges.subscribe(res => {
        this.dataSource.filter = JSON.stringify(res);
    });
 
  }

  customFilter = (data: any, filter: string) => {
    const filterData = JSON.parse(filter);
    return Object.keys(filterData).reduce((a: boolean, key: string) => {
      if (!a) return false;
      if (filterData[key])
        return ('' + data[key]).indexOf('' + filterData[key]) >= 0;
      return true;
    }, true);
  };


    //And when you define the dataSource you have
    this.dataSource.filterPredicate = this.customFilterPredicate();

Well the customFilter is a bit complex

We use reduce. reduce it's only a way to loop thought of elements of one array and return an unique result. you can see as a simple loop like this

array.reduce(
       (a:variable where store the result,key:element of the array)
        ,function()=>{...}, 
       initial value
)
let a=true //<--the first value
for (let i=0;i<=Object.keys(filterData).length;i++)
{
    const key=Object.keys(filterData)[i]
    if (!a) 
        continue
    else
    {
       if (filterData[key]) //<--if have any value the "field"
          a=('' + data[key]).indexOf('' + filterData[key]) >= 0;
       else
          a=true
    }
}
//we get the value of "a"
console.log(a)

To the last part, the custom filter I wrote a stackblitz to see how works

NOTE: perhafs you need check againts toUpperCase()