Filter on multiple columns using one pipe angular 2

49,693

Solution 1

Here is a solution using the object passed as multiple columns filter. I found it more convenient then passing a 2D array:

    @Pipe({
        name: 'filter'
    })
    export class FilterPipe implements PipeTransform {
        transform(items: Array<any>, filter: {[key: string]: any }): Array<any> {
            return items.filter(item => {
                const notMatchingField = Object.keys(filter)
                                             .find(key => item[key] !== filter[key]);

                return !notMatchingField; // true if matches all fields
            });
        }
    }

Having an array of objects with multiple columns:

this.people = [
  {name: 'John', age: 27, sex: 'male'},
  {name: 'Lara', age: 21, sex: 'female'},
  {name: 'Rick', age: 29, sex: 'male'},
  {name: 'Eva',  age: 27, sex: 'female'},
  {name: 'Mike', age: 27, sex: 'male'}
];

And a filter:

this.peopleFilter = {age: 27, sex: 'male'};

Use it like:

 <div *ngFor="let person of people | filter: peopleFilter;"></div>

As a result, two people are matching our criteria: John and Mike.

Here is the working plunker: Multiple columns filter pipe demo.

Solution 2

Here is what I did with Angular 8:

Goal: Search on multiple properties say "Property1" and "Property2" from a list of items for a given keyword:

app.module.ts:

......
import { MyFilterPipe } from './shared/pipes/my-filter.pipe';

@NgModule({
  declarations: [
    ...
    MyFilterPipe
  ],
  imports: [
    ...
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Pipe: content-filter.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'myFilter'
})
export class MyFilterPipe implements PipeTransform {

  transform(items: any[], keyword: any, properties: string[]): any[] {
    if (!items) return [];
    if (!keyword) return items;
    debugger;
    return items.filter(item => {
      var itemFound: Boolean;
      for (let i = 0; i < properties.length; i++) {
        if (item[properties[i]].toLowerCase().indexOf(keyword.toLowerCase()) !== -1) {
          itemFound = true;
          break;
        }
      }
      return itemFound;
    });

  }
}

component:

<input type="search" class="form-control filter-list-input" placeholder="Filter"
                  aria-label="Filter" name="search" [(ngModel)]="searchText" >

    <div *ngFor="let itemof myItems | myFilter:searchText:['Property1', 'Property2']; let i = index">...
</div>

component.ts:

export class MyListComponent implements OnInit {

  ...
  searchText: string;

Solution 3

Replace Your code as below,

 export class DataFilterPipe implements PipeTransform {
   transform(value: Item[], field: string, args: string): Item[]{
      let filter: string = args ? args.toLocaleLowerCase() : null;
      return filter ? value.filter((item : Item) =>
          Item[field].toLocaleLowerCase().indexOf(filter) != -1) : value;
   }
}

In Html page,

 <tbody *ngFor="let item of items | dataFilter : columnName : value ">

Solution 4

I am assuming that you have an array with columns like this:

[{col1:"col1",col2:"col2",col3:"col3"}]

I have also omitted all type-checkin, null-pointers, and error-handling. The current solution is an example I tried with the array:

myData:Array<any> = [{col1:"a",col2:"b",col3:"cadd"},
    {col1:"abba",col2:"bobba",col3:"cadd"},
    {col1:"abba",col2:"bobba",col3:"cool"},
    {col1:"a",col2:"bobba",col3:"cool"}];

and the pipe:

@Pipe({
  name: 'dataFilter'
})
export class DataFilterPipe implements PipeTransform {

  transform(value: any, args?: any): any {
    return value.filter(item =>{
      var matchesAll = true;
      for(var i = 0; i<args.length; i++){
        // check if your data contains the column and the value defined in args.
        if(item.hasOwnProperty(args[i][0]) && item[args[i][0]]==args[i][1]){
          continue;
        }else{ // at least one column did not match,
          matchesAll = false;
        }
      }
      return matchesAll;
    });
  }

}

You can then call

dataFilter.transform(myData,[["col1","abba"],["col2","bobba"],["col3","cool"]]);

in order to get one result, which is row number 3 after transformation: [{col1:"abba",col2:"bobba",col3:"cool"}].

Note: You may have to adjust the names of the columns in my example to make it work with your code.

EDIT: With this solution, you can also pass arbitrary number of columns.

e.g dataFilter.transform(myData,[["col3","cool"]]);

which will result in the two last rows (from my example) after transformation:

[{col1:"abba",col2:"bobba",col3:"cool"},{col1:"a",col2:"bobba",col3:"cool"}]

EDIT: after comment stating that the code is not working, I provided a plunkr of the example above: https://plnkr.co/edit/VdpGJWyzWUVFzYNDSz1g

Share:
49,693
Jeeten Parmar
Author by

Jeeten Parmar

Updated on May 21, 2021

Comments

  • Jeeten Parmar
    Jeeten Parmar almost 3 years

    I am trying to filter Array data based on multiple columns using only one Pipe. Right now, It filters first column value. Please check my below code and help me to sort this out.

    My Code:

    @Pipe({ name: "dataFilter", pure: false })
    export class DataFilterPipe implements PipeTransform {
        transform(value: Array<any>, filter: any[]) {
            if (!filter) {
                return value;
            } else if (value) {
                return value.filter(item => {
                    for (var i = 0; i < filter.length; i++) {
                        if (filter[i][1] == undefined) {
                            return true;
                        } else if ((typeof item[filter[i][0]] === 'string' || item[filter[i][0]] instanceof String) &&
                            (item[filter[i][0]].toLowerCase().indexOf(filter[i][1]) !== -1)) {
                            return true;
                        }
                        return false;
                    }
                });
            }
        }
    }
    

    I am passing data like dataFilter : [['column1',value1],['column2',value2],['column3',value3]].