Aya Salama Aya Salama - 3 months ago 38
AngularJS Question

filter category and subcategory with the same search box in angularjs

I'm new with angularjs, so i'm not sure if this is a stupid question or not,
I have two nested lists which contains categories and sub categories, 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}">
<ul>
<li ng-repeat="subcategory in category.subcategories"></li>
</ul>
</li>
</ul>


now i want to make the same box search in both categories and subcategories
so that if the value matched with a category then the category and its subcategories will be shown,

else if it matched with subcategory the category will show and only matched subcategory will show below it,
if it 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}">
<ul>
<li ng-repeat="subcategory in category.subcategories | filter: { subcategory_name:filter_categories_subcategories}"></li>
</ul>
</li>
</ul>


but it doesn't work as it show only category or channel

Answer

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).