Search code examples
angularjsangularjs-filter

AngularJS nested filter hiding elements without destined property


I'm using AngularJS on a project and I need to implement a select box with a filter to a nested property. My object (I have an array of those and I'm iterating through them via ng-repeat) has a structure similar to this:

{
  id: 1,
  name: 'Example',
  groups: [
    { id: 1, name: 'Group 1' },
    { id: 2, name: 'Group 2' }
  ]
}

I need to filter the group ID of the elements, and after searching I've come up with two ways to do it:

1.

| filter: { $: { id: search.item_id } }

Which has these problems:

Apparently it searches for any nested properties named ID, so if I have more than one object inside my main object, with a property called ID too, it would add it to the filter. Example:

{
  id: 2,
  name: 'Example 2',
  groups: [
    { id: 1, name: 'Group 1' },
    { id: 2, name: 'Group 2' }
  ],
  categories: [
    { id: 1, name: 'Cat 1' },
    { id: 2, name: 'Cat 2' }
  ]
}

Filtering for ID 1 would select not only group with ID 1, but category with ID 1 too.

Also, with this method, even before setting the filter (search.item_id model is null), objects without groups are being filtered and not appearing in the list. These objects are like this:

{
  id: 3,
  name: 'Example 3',
  groups: []
}

and the other way is:

2.

| filter: { groups: [{ id: search.item_id }] }

In this case, the problem is that it simply doesn't work, it filters everything, leaving the list blank, no matter if it's set or which option is selected.

How can I make it work? I've been searching and couldn't find anything about this. Filtering nested properties is (or should be) a very basic thing.


Update:

So, xtx first solution kinda did it, but only if I use input text or number, but I need to use a select box (more specifically uib-dropdown, but working in a regular select is the next step). My select box is looking like this:

<select name="filter_classes_groups_test" ng-model="search.group_id">
  <option val="group.id" ng-repeat="group in classesGroups">{{ group.name }}</option>
</select>

When I interact with it, nothing happens.


Solution

  • If creating a custom filter works for you, here is how you can do that:

    app.filter('groupsFilter', function() {
      return function(input, groupId) {
        var out = [];
    
        if (!groupId || isNaN(parseInt(groupId))) {
          return input;
        }
        angular.forEach(input, function(item) {
          if (item.groups && angular.isArray(item.groups)) {
            angular.forEach(item.groups, function (group) {
              if (group.id === parseInt(groupId)) {
                out.push(item);
              }
            });
          }
        });
        return out;
      }
    });
    

    As you can see, the custom filter has name groupsFilter, and takes group id to search for as a parameter. The filter can be applied like this:

    <div ng-repeat="item in data | groupsFilter:search.item_id">
      ...
    </div>
    

    UPDATE:

    Instead of creating a custom filter, you can just create a function that implements filtering logic, in scope like this:

    $scope.groupsFilterLocal = function(value) {
      if (!$scope.search.item_id || isNaN(parseInt($scope.search.item_id))) {
        return true;
      }
      if (!value || !value.groups || !angular.isArray(value.groups)) {
        return false;
      }
      for (var i = 0; i < value.groups.length; i++) {
        if (value.groups[i].id === parseInt($scope.search.item_id)) {
          return true;
        }
      }
      return false;
    };
    

    and then apply it using build-in filter like this:

    <div ng-repeat="item in data | filter:groupsFilterLocal ">
      ...
    </div>
    

    Notice that in this case you can't pass the value to search for (search.item_id) into your function groupsFilterLocal like it is done for the custom filter groupsFilter above. So groupsFilterLocal has to access search.item_id directly


    UPDATE 2: How to create a select properly

    The reason why the filter is not applied when you pick a group in your dropdown, is in the way how you defined the select. Instead of the id of the selected group, search.group_id gets assigned group's name. Try defining the select like shown below:

    <select name="filter_classes_groups_test" ng-model="search.item_id" ng-options="group.id as group.name for group in classesGroups">
        <option value="">-- Choose Group --</option>
    </select>
    

    To ensure that search.item_id gets id of the group that is selected (and not its name), try temporarily adding {{ search.item_id }} somewhere in your template.