Reputation: 8970
I have an ngFor
that is iterating over an array of objects. I display this information in my table on the UI and it all works fine.
I am trying to implement a little filter box so I can narrow down the results based on what is entered into the box.
I am using a pipe
for this and had it working with an array of data but I am not sure how to search through objects without specifying a specific key. I want to be able to enter search term
and if it is a value in any one of the objects, filter it.
Pipe:
@Pipe({ name: 'filter' })
export class FilterPipe implements PipeTransform {
public transform(values: any[], filter: string): any[] {
if (!values || !values.length) return [];
if (!filter) return values;
return values.filter(v => v.indexOf(filter) >= 0);
}
}
Component:
dataObj = [
{
name: 'Bob',
age: 21,
location: 'USA'
},
{
name: 'Sally',
age: 25,
location: 'UK'
}]
filterString = '';
HTML:
<div>
<h2>Hello {{name}}</h2>
<input [(ngModel)]="filterString" />
<div *ngFor="let d of (dataObj | filter: filterString)">
{{ d.name }} - {{ d.age }} - {{ d.location }}
</div>
</div>
Desired Outcome:
If I entered 21
or Sally
or US
, I would expect to see results. I was trying to avoid hard coding a key into my pipe that it searches on as I wanted all values within the object to be searchable.
Here is a plnkr example: https://plnkr.co/edit/ubLyB152hgrPJSVp8xSB?p=preview
Upvotes: 2
Views: 1157
Reputation: 2132
According to the Angular documentation, the most recommended is that you use a function to perform the filter and avoid using pipes in * ngFor. In this function, you can use the desired resources to perform the filter on your object collection as mentioned.
To identify the items that match the text entered in the filter field, use a component that searches the nested fields and for that, I published the component ( wngx-filter ) that I am using in npm.
That way, your code might look like this:
For this data structure (can be use a complex type object reference or interface):
invoices: any[] = [
{
general: {
number_invoice: "996"
},
note_invoice: "0001",
state_invoice: "pending",
customer_invoice: "Johan Corrales",
date_invoice: "2018-10-30",
days_invoice: "30",
expiration_invoice: "2018-11-30",
payment_invoice: "Credit"
}
]
1 - Inject the pipe component into the constructor to be used by the function.
constructor(private pipefilter: WfilterPipe) {}
2 - Create a function that will receive the data collection to perform the filter. In this function, you define which attributes you want to be used for the filter according to the example below:
filterFunction(collection: any[]): any[] {
return this.pipefilter.transform(collection, [
{ field: "general.number_invoice", value: this.filterInvoice }, // nested property
{ field: "note_invoice", value: this.filterInvoice },
{ field: "customer_invoice", value: this.filterInvoice },
{ field: "payment_invoice", value: this.filterInvoice }
]);
}
Note that the "general.number_invoice" attribute is nested, that is, within a complex type. The wngx-filter component can fetch data in infinite sub-levels (nested).
And use this function in html like this:
<li *ngFor="let invoice of filterFunction(invoices)">
...
</li>
As a complete demonstration of component usage, you can access stackblitz and see the code as it is simple.
Upvotes: 0
Reputation: 18359
You can iterate through all object keys via Object.keys(o)
, and check if there is some match in at least one object field.
You will also need to handle the type of v[k]
, as indexOf
is there only for strings (and arrays), not for numbers.
Something like this should do the job:
public transform(values: any[], filter: string): any[] {
if (!values || !values.length) return [];
if (!filter) return values;
return values.filter(v => {
let match = false;
Object.keys(v).forEach(k => {
if (typeof v[k] === 'string') {
match = match || v[k].indexOf(filter) >= 0;
} else {
match = match || v[k] == filter; // == intentinally
}
});
return match;
});
}
Here is your plunker with this fix: https://plnkr.co/edit/JoJ8M6YoID2yU6ASGEXf?p=preview
Upvotes: 3