Skip to content

Filtering an array of objects by user defined properties based on user inputted search term

That title might not make a lot of sense but please bear with me and I’ll try to explain what I’m after.

I’m creating an Angular filter component that I can plug and play into various portions of my app. However, the question itself is more of a JavaScript question than an Angular one.

What I want to achieve is quite simple in theory but seems to be hard in practice (at least for me).

I want to give the user the ability to input an array of objects, and an array of object property names. I then want to filter the array of objects by either property 1 OR property 2.

Lets say we have the following setup:

inputtedObjects = [
  {name: 'Bruce', gamerTag: 'BruceyBoi', email: '[email protected]'}, 
  {name: 'Frank', gamerTag: 'BruceIsMyNemesis', email: '[email protected]'},
  {name: 'Steve', gamerTag: 'ScubaSteve', email: '[email protected]'}

];

filterProperties = ['name', 'gamerTag']

What I then want to do is essentially this operation:

let filteredObjects = inputtedObjects.filter(object => 
  object[filterProperties[0]].toLowerCase().includes(searchTerm) ||
  object[filterProperties[1]].toLowerCase().includes(searchTerm)

Where the searchTerm is a user inputted field from an input tag in HTML.

This would result in if the user was typing in “bruce” in the input, he would get the top two filtered results returned to him.

I have tried the following code:

      let currentObjects = this.objects;

      this.filterProperties.forEach(field => {
        this.filteredObjects = currentObjects.filter(
          object =>
            object[field]
              .toLowerCase()
              .includes(searchTerm.toLowerCase())
        );
      });

However, the issue with the code above is that it filters as an AND and not an OR in the sense that it would work but if the user wrote “bruce” it would only return the first object as both of the properties must include “bruce” for the above code to work.

Now I can do this with some kind of switch case, as in if the filterProperties array is length 1 then we do:

let filteredObjects = inputtedObjects.filter(object => 
  object[filterProperties[0]].toLowerCase().includes(searchTerm)

and if it’s of length 2 we do:

let filteredObjects = inputtedObjects.filter(object => 
  object[filterProperties[0]].toLowerCase().includes(searchTerm) ||
  object[filterProperties[1]].toLowerCase().includes(searchTerm)

Etc.

Now obviously this is not very clean code, nor does it seem very efficient whatsoever. It’s also not easy to scale and it would require some kind of error message if the user attempted to input too many “filterProperties” as it would depend on the amount of hardcoded switch case statements (bad code smell already).

What I would want to achieve then is for the user to be able to provide an array of infinite objects of a certain type with potentially hundreds of properties per object. Then the user says, I want to filter on these 6 property names, and then begins to type “test”, it would then evaluate objects that match test on any one of these 6 properties provided. Not only objects that match test on all of these properties.

Any ideas on how I could potentially achieve this outcome?

Answer

You can use Array.some on the filterProperties array to see if one (or more) of the object properties contains the searchTerm:

inputtedObjects = [{
    name: 'Bruce',
    gamerTag: 'BruceyBoi',
    email: '[email protected]'
  },
  {
    name: 'Frank',
    gamerTag: 'BruceIsMyNemesis',
    email: '[email protected]'
  },
  {
    name: 'Steve',
    gamerTag: 'ScubaSteve',
    email: '[email protected]'
  }

];

filterProperties = ['name', 'gamerTag'];

searchTerm = 'bruce';

filteredObjects = inputtedObjects.filter(object =>
  filterProperties.some(p => object[p].toLowerCase().includes(searchTerm))
);

console.log(filteredObjects);