Angular Filter Checkboxes

Home / Angular Filter Checkboxes

A fellow developer asked me how filter Pipes could be used for filtering based on checkboxes. Using the existing code samples I’ve created, I endeavored to put together a little demo of how this can be achieved.


Angular’s custom Pipes can, really, achieve nearly any filtering you’d want. The idea of filtering with checkboxes means that you have discrete values associated with the checkboxes, when checked, and you want to compare those checked values against properties within an array of objects. Sounds straight-forward, right?

The first hurdle is handling checkboxes in Angular. I decided to use an ngFor, and various bindings, for this purpose to display a list of checkboxes.

<div class="form form-inline">
  <div *ngFor="let filterItem of filterItems; let i = index;" class="form-check" style="padding-right:20px;">
    <label class="form-check-label">
      <input type="checkbox" class="form-check-input" [(ngModel)]="filterItem.checked" [value]="filterItem.value"> Value {{ i + 1 }}
    </label>
  </div>
</div>

<pre>Selected items: <span *ngFor="let filterItem of checked()" style="padding-right:10px;">{{ filterItem.value }}</span></pre>

You can see I’m using Angular Forms’ ngModel to find the checked property to my “filterItem” and mapping the [value] attribute to the filterItem’s value property. The attribute could be left out since all I’m really interested in knowing is whether the checkbox is checked.

I’m also using an ngFor to display which filterItems are checked. I call a simple “checked” method that returns the checked filterItems. This uses the simple Array.prototype.filter method.

checked() {
  return this.filterItems.filter(item => { return item.checked; });
}

The final part of the HTML mark-up is to display the list of items that is being filtered.

<ul>
  <li *ngFor="let item of items | filter: { property: ''} : checked() : false">{{ item.name }} - {{ item.property }}</li>
</ul>

You can see the filter signature in the mark-up. I’m passing in an object that will contain the filter keys, the array of checked items, and a boolean indicating whether AND filtering is applied. Using “and” probably doesn’t make a lot of sense unless there are multiple property keys with only a single checked item. Having multiple checked items with only a single property, obviously, would always return an empty filtered list.

The filter itself doesn’t deviate much from the other filters I’ve written. The only major difference is that the checked array has to be reduced and the “checked.value” is compared against each item’s property key values.

@Pipe({
    name: 'filter'
})
export class FilterPipe implements PipeTransform {
  transform(items: any, filter: any, filterItems: Array<any>, isAnd: bool): any {
    console.log('Filtering ..');
    if (filter && Array.isArray(items) && filterItems) {
      let filterKeys = Object.keys(filter);
      let checkedItems = filterItems.filter(item => { return item.checked; });
      if (!checkedItems || checkedItems.length === 0) { return items; }
      if (isAnd) {
        return items.filter(item =>
            filterKeys.reduce((acc1, keyName) =>
                (acc1 && checkedItems.reduce((acc2, checkedItem) => acc2 && new RegExp(item[keyName], 'gi').test(checkedItem.value) || checkedItem.value === "", true))
              , true)
              );
      } else {
        return items.filter(item => {
          return filterKeys.some((keyName) => {
            return checkedItems.some((checkedItem) => {
              return new RegExp(item[keyName], 'gi').test(checkedItem.value) || checkedItem.value === "";
            });
          });
        });
      }
    } else {
      return items;
    }
  }
}

The Pipe has, like I mentioned, the additional some/reduce of the checked items. This is used to compare each key specified against the value of the checked item using simple regular expressions.

The plunk below shows everything in action with a default “OR” matching. By default, if the checked items is empty, then all items are shown.

4 thoughts on “Angular Filter Checkboxes”

  1. Can you please create a similar example when we have filter it using a different set of check boxes ? To be clear for example there are check boxes for category and price range. I need to filter all categories in a particular price range.

    1. If I understand right, you want essentially a range check to replace the RegEx check, correct?

      It should be only a matter of replacing this:

      new RegExp(item[keyName], 'gi').test(checkedItem.value)
      

      With something like this:

      (+item[keyName] >= checkedItem.lowerLimit && +item[keyName] <= checkedItem.upperLimit)
      

      Of course, this is making the assumption that you will add upper and lower limit properties to your comparison object(s). I added the “+” unary operator in case they are strings to convert to numbers.

  2. Hello, and thanks to this clear solutions using checkboxes with pipes.
    it work almost 100%.

    The question i would like to ask is how come i should apply for the “filter: any” when inside the item, the “property” have an array.

    By now is working well when the filter only has 1 value >> “property”: [“cca”].
    but the same property with more than 1 value, is not returning this item when i apply checkbox with vale “cca” cannot fins any of them: “property”: [“cca”,”push”, “sms”].
    i would like lo show all items, with the property “cca”, not only the ones who has 1 item in the arrray.

    Thanks in advance.

    Let’s exemplify

    1. You would need apply a filtering/mapping based on the values within property by checking whether it is an array or not … Although, that was never the intent of the original demo. It was intended to simply check if the value of a scalar property was contained within an arbitrary array of values… Anywho, something like this who would check to see if the property is an array and if any value within the first array is contained within the property’s array ..

      var filterItems = [
      	{ value: 'cca', checked: false },
      	{ value: 'push', checked: true },
      	{ value: 'sms', checked: true }
      ];
      var checkedItems = filterItems.filter(item => { return item.checked; });
      
      var arr = [
      	{ prop: [ "cca" ] },
      	{ prop: [ "cca","sms" ] },
      	{ prop: [ "push","sms" ] },
      	{ prop: [ "push" ] },
      	{ prop: [ "sms" ] }
      ];
      
      var keyName = "prop";
      var matchedItems = arr.filter(item => {
      	return checkedItems.some((checkedItem) => {
      		if (Array.isArray(item[keyName])) {
      			return item[keyName].some(val => {
      				return new RegExp(val, 'gi').test(checkedItem.value) || checkedItem.value === "";
      			});
      		} else {
      			return new RegExp(item[keyName], 'gi').test(checkedItem.value) || checkedItem.value === "";
      		}
      	})
      });
      

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.