In one of my previous posts, I touched on how to build a filtered list using Angular pipes. As I’m adding more features to the table component that I’m working, I spent a little time modifying the pipe to be able to performing combinatory filtering.
The built-in JavaSciprt sorting, filtering (some), reducing, and other array prototypical methods are quite powerful. In the previous filtered list component, I used these methods to reduce a set of property keys passed into the filter and perform a RegEx expression to find matches within the array where any item’s key value matched the filter value.
One modification I made to the filtered list’s HTML was to pass in multiple properties to the pipe:
<ul> <li *ngFor="let item of _items | filter:{ label: filterText, description: filterText } : false">{{ item.value }} - {{ item.label }} - {{ item.description }}</li> </ul>
You can see that the first argument to the filter pipe is an object with keys set to whatever the user typed into the filter textbox. Notice, though, that there is a second paramaeter (separated by a colon delimiter) that is a boolean. This is one very nice thing about pipes. You can define whatever arguments you want within the transform method and you then must only pass them into the pipe in the proper order.
What is this bool? I use it to distinguish whether the filter pipe will match all of the items properties against those passed in on the filter data/object (and), or if only one of them has to match (or). False indicates that we are performing an “or” search.
The filter pipe has to be modified accordingly. It access the “isAnd” parameter along with the original filter data. It uses the “isAnd” to toggle between two different matching routines. When “isAnd” is true, the filtering is the same as before. But, when it’s false, the “some” method is used to see if there are any properties that match the key values.
@Pipe({ name: 'filter' }) export class FilterPipe implements PipeTransform { transform(items: any, filter: any, isAnd: bool): any { if (filter && Array.isArray(items)) { let filterKeys = Object.keys(filter); if (isAnd) { return items.filter(item => filterKeys.reduce((memo, keyName) => (memo && new RegExp(filter[keyName], 'gi').test(item[keyName])) || filter[keyName] === "", true)); } else { return items.filter(item => { return filterKeys.some((keyName) => { console.log(keyName); return new RegExp(filter[keyName], 'gi').test(item[keyName]) || filter[keyName] === ""; }); }); } } else { return items; } } }
Both searches are performing RegEx value matching to filter the data. You can see based on the fact that I am passing in “{ label: filterText, description: filterText }” into the pipe, that with the “or” filter, if there are matches against either of those properties on a list item, the list item will be displayed.
You can probably see where I’m going with this in the context of having a searchable table. Within a table component, I could build up the filter data based on the column definitions pretty easily and perform the filtering of rows during a callback/UI event. That’s next on my radar.
Below is a working plunk with the “or” search on the unordered list.
Hi, Interesting solution it is. I am having a quite alike problem and I am not able to get it right.
Problem: filter array from map of array.
ex: arrToFilter = [{keyA=””,keyB=””,id=””}];
map={keyA:[“A”,”B”,”C”],keyB:[“BA”,”BB”,”BC”]}
so now I want to filter first array based on both of these map array at a time. Can you help me out with this?