Dilshan Liyanage
Dilshan Liyanage

Reputation: 4558

Multiple Dynamic Filters in JavaScript Array

I can't seem to think about how I can overcome this issue where there might be any amount of filters as objects which will help me to filter out the data array.

  data = [
    {
      id: 1,
      first_name: 'Colver',
    }, {
      id: 2,
      first_name: 'Brodie',
    }, {
      id: 3,
      first_name: 'Philippa',
    }, {
      id: 4,
      first_name: 'Taite',
    }, {
      id: 5,
      first_name: 'Pierson'
    }
  ];

  filters = [
    {
      field: 'id',
      operator: 'between',
      value: '2-5'
    },
    {
      field: 'first_name',
      operator: 'eq',
      value: 'Philippa'
    }
  ];

  ngOnInit(): void {
    const filteredItems = [];
    this.data.forEach(item => {
      this.filters.forEach((filter, filterIndex) => {
        const itemValue = item[filter.field];
        switch (filter.operator) {
          case 'eq':
            if (itemValue === filter.value) {
              filteredItems.push(item);
            }
            break;
          case 'between':
            const [firstValue, secondValue] = filter.value.split('-');
            if (itemValue > firstValue && itemValue < secondValue) {
              filteredItems.push(item);
            }
            break;
        }
      });
    });
    console.log(filteredItems);
  }

I basically want the filteredItems to output like below since the id is between 2 and 5 and the first_name is Philippa. But since I'm iterating the filters 2 times both the times items gets pushed to filteredItems.

 [{
      id: 3,
      first_name: 'Philippa',
  }]

Upvotes: 0

Views: 1588

Answers (4)

Reizo
Reizo

Reputation: 1437

Instead of using

const filteredItems = [];
this.data.forEach(item => {
  // [...]
  filteresItems.push(item)
  // [...]
});

use Array's filter:

const filteredItems = this.data.filter(item => {
  // [...]
  let match = true; // or false
  return match;
});

Taking your whole example, you could use:

function passes(item, filter) {
  const itemValue = item[filter.field];
  switch (filter.operator) {
    case 'eq':
      if (itemValue === filter.value) {
        return true;
      }
    case 'between':
      const [firstValue, secondValue] = filter.value.split('-');
      if (itemValue > firstValue && itemValue < secondValue) {
        return true;
      }
  }
  return false;
}

const filteredItems = this.data.filter(
  item => this.filters
    .map(filter => passes(item, filter))
    .every());

Upvotes: 0

Nina Scholz
Nina Scholz

Reputation: 386520

You could take Array#every and an object for getting the right operator function.

const
    data = [{ id: 1, first_name: 'Colver' }, { id: 2, first_name: 'Brodie' }, { id: 3, first_name: 'Philippa' }, { id: 4, first_name: 'Taite' }, { id: 5, first_name: 'Pierson' }],
    filters = [{ field: 'id', operator: 'between', value: '2-5' }, { field: 'first_name', operator: 'eq', value: 'Philippa' }],
    operators = {
        between: (field, range) => {
            const [min, max] = range.split('-').map(Number);
            return min <= field && field <= max;
        },
        eq: (field, value) => field === value
    },
    result = data.filter(o =>
        filters.every(({ field, operator, value }) =>
            operators[operator](o[field], value)
        )
    );
  
console.log(result);

Upvotes: 3

Aplet123
Aplet123

Reputation: 35482

Use Array.prototype.every to make sure every filter passes, and if so, push it to the array:

ngOnInit(): void {
  const filteredItems = this.data.forEach(item => 
    this.filters.every((filter, filterIndex) => {
      const itemValue = item[filter.field];
      switch (filter.operator) {
        case 'eq':
          if (itemValue === filter.value) {
            return true;
          }
          break;
        case 'between':
          const [firstValue, secondValue] = filter.value.split('-');
          if (itemValue > firstValue && itemValue < secondValue) {
            return true;
          }
          break;
      }
      return false;
    })
  );
  console.log(filteredItems);
}

Upvotes: 0

Unmitigated
Unmitigated

Reputation: 89139

You can perform a reduce operation over the filters array and use Array#filter to remove objects on each iteration.

const data = [
    {
      id: 1,
      first_name: 'Colver',
    }, {
      id: 2,
      first_name: 'Brodie',
    }, {
      id: 3,
      first_name: 'Philippa',
    }, {
      id: 4,
      first_name: 'Taite',
    }, {
      id: 5,
      first_name: 'Pierson'
    }
  ],
  filters = [
    {
      field: 'id',
      operator: 'between',
      value: '2-5'
    },
    {
      field: 'first_name',
      operator: 'eq',
      value: 'Philippa'
    }
  ];
const res = filters.reduce((acc,{field,operator,value})=>
   acc.filter(o => operator === 'eq' && o[field] === value || 
   operator === 'between' && o[field] >= value.split('-')[0] 
   && o[field] <= value.split('-')[1]), data);
console.log(res);

Upvotes: 0

Related Questions