2

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

1

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

4 Comments

This seems to work fine until it has narrowed it down to a single result, it then doesn't show anything. For Example, if I typed "U" it would find both but if I entered "USA" and theres only one result, it shows nothing
I think thats because of using indexOf also on numbers, see my edited answer. I also added plunker that seems to work ok.
Thanks, this looks good. It was throwing me off due to it being case sensitive so it was omitting things. I will throw on something for that.
If you want case insensitive search, just call str.toLowerCase() on both filter and v[k] properties (again, for v[k] you can do this only when its string).
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.