Search code examples
htmlangularjsangularjs-filter

filter category and subcategory with the same search box in angularjs


I have two nested lists which contain categories and subcategories, a search box that I use to filter categories only so my code was like that

<input type="text" ng-model="filter_categories">
<ul>
    <li ng-repeat="category in menu | filter: {name:filter_categories}">
        {{ category.name }}
        <ul>
            <li ng-repeat="subcategory in category.subcategories"> {{ subcategory.name }}</li>
        </ul>
    </li>
</ul>

now i want to make the same box search in both categories and subcategories so that if the value is matched with a category then the category and all its subcategories will be shown,
else if it matched with subcategory, the category will show and only matched subcategory will be shown below it, if it is matched with both, then it will be like the first case (the category and its subcategories will be shown)

I tried something like that

<input type="text" ng-model="filter_categories_subcategories">
<ul>
    <li ng-repeat="category in menu | filter: {name:filter_categories_subcategories}">
        {{ category.name }}
        <ul>
            <li ng-repeat="subcategory in category.subcategories | filter: { subcategory_name:filter_categories_subcategories}"> {{ subcategory.name }} </li>
        </ul>
    </li>
</ul>

but it doesn't work as it shows only category or subcategory


Solution

  • You can use a filter function to implement the logic you want.

    The filter function could set a "forceDisplay" variable on its subcategories if its name matches the search you're doing. Then you display all categories that have a forceDisplay variable set to true, and you display the ones that do not have this forceDisplay but match the search input (their parent didn't match the search but they do).

    I strongly recommend using ng-model-options with a debounce value to avoid doing all those computations too often.

    $scope.displayCategory = function(category) {
      if (category.name.indexOf($scope.filter_categories_subcategories) !== -1) {
        // if we display a category, then force the display of its subcategories
        for(var i = 0; i < category.subcategories.length; i++) {
          category.subcategories[i].forceDisplay = true;
        }
        return true;
      }
      var hasOneDisplayedSubcategory = false;
      for(var i = 0; i < category.subcategories.length; i++) {
        // reset the forceDisplay variable before checking if categories should be displayed
        var subcategory = category.subcategories[i];
        subcategory.forceDisplay = false;
        if (!hasOneDisplayedSubcategory && $scope.displaySubcategory(subcategory)) {
          hasOneDisplayedSubcategory = true;
        }
      }
      return hasOneDisplayedSubcategory;
    };
    $scope.displaySubcategory = function(subcategory) {
      // if the forceDisplay variable is set, it means we're in a subcategory
      if (subcategory.forceDisplay) {
        return true;
      }
      if (subcategory.name.indexOf($scope.filter_categories_subcategories) !== -1) {
        return true;
      }
      return false;
    };
    

    and in your html

    <input type="text" ng-model="filter_categories_subcategories">
    <ul>
        <li ng-repeat="category in categories | filter: displayCategory">
            {{ category.name }}
            <ul>
                <li ng-repeat="subcategory in category.subcategories | filter:displaySubcategory">
                   {{ subcategory.name }}
                </li>
            </ul>
        </li>
    </ul>
    

    Working plunkr example

    An alternative way would be to create a reference from subcategories to their parent, and check in the displaySubcategory function if their parent should be displayed (compute the forceDisplay variable every time instead of storing it).