1

I have 2 nested ng-repeats to populate a list of categories and items under those categories. I am trying to implement a search filter so:

  • If the title of an item contains the string of the search filter, this item and its category should be displayed.
  • If the name of the category contains the string of the search filters, this category should be displayed. If non of the items under this category match the filter, all the items under the category should be displayed.
  • The search filter is bind to the model of an input field.
  • I have tried to implement a custon filter to loop through the categories and populate an array based on the title of the items or the name of the category, but it creates an infinite loop.

    <li data-ng-repeat="group in vars.filteredCategories = (allCategories | customFilter:filters.searchText)">
    {{group.category}}
    <ul>
    <li data-ng-repeat="item in group.items | orderBy:'title'">{{item.title}}</li>
    </ul>
    

Controller:

$scope.allCategories = [{"category":"cat1","items":[{"id":10015,"title":"Test","category":"cat1"},{"category":"cat2","items":[{"id":10015,"title":"Test2","category":"cat2"}];

Custom filter (which doesn't work)

.filter('customFilter', function () {
        return function (categories, search) {
            var filtered = [];

            var itemsPushed = [];
            if (search !== '') {
                angular.forEach(categories, function (category) {
                    if (category.category.toLowerCase().indexOf(search.toLowerCase()) !== -1) {
                        filtered.push(category);

                    } else {
                        if (angular.isDefined(category.items)) {

                            var itemsInside = angular.copy(category.items);
                            category.items = [];

                            for (var x = 0; x < itemsInside.length; x++) {
                                if (itemsInside[x].title.toLowerCase().indexOf(search.toLowerCase()) != -1 && itemsPushed.indexOf(itemsInside[x].id) == -1) {
                                    category.items.push(itemsInside[x]);
                                    itemsPushed.push(itemsInside[x].id);

                                }
                            }
                            filtered.push(category);
                        }
                    }
                });
            } else {
                filtered = categories;
            }

            return filtered;
        };
    })
5
  • I believe your loop is because you're modifying category.items. Can you remove the big "else" part of your filter and tell me if you still get that error ? Commented Mar 6, 2017 at 2:57
  • Thanks for your reply. If I remove that part I don't the the loop, but it doesn't work as I need it to. because I need it to add the items to filtered only if they match the filter if the category doesn't match the filter. Commented Mar 6, 2017 at 3:43
  • but did the infinite loop stopped? It is just to be sure that what I said was the problem. Then we can work from there Commented Mar 6, 2017 at 3:44
  • Yes, it does. Sorry, I thought I had said that in my message but I had a typo. Thanks Commented Mar 7, 2017 at 6:11
  • is your data correct ? I was reviewing it for a fiddle and I see only one item with 3 depth level. Check $scope.allCategories if it is written correctly Commented Mar 7, 2017 at 14:41

1 Answer 1

2

Check if this is what you want http://plnkr.co/edit/zt2BJEpRv2EBbFf32rBC?p=preview

The code might need to be modified if you want case insensitive comparisons.

Controller

app.controller('MainCtrl',function ($scope) {
  $scope.filterText = '';
  $scope.data = [
    {"category":"cat1",
      "items":[{"id":10015,"title":"Test","category":"cat1"}, {"id":10015,"title":"Other","category":"cat1"}]
    },
    {"category":"cat2",
      "items":[{"id":10015,"title":"Test2","category":"cat2"}]
    }];
});

filter

app.filter('customFilter', function(){
  return function(data, searchString) {
    if (searchString === '') {
      return data;
    }
    // don't modify original state
    var newData = [];
    data.forEach(function(group){
      // If the name of the category contains the string of the search filters, 
      // this category should be displayed. If non of the items under this category match the filter,
      // all the items under the category should be displayed.
      var matchCategory = group.category.indexOf(searchString) > -1;
      var nonItemMatches = group.items.every(function(item){
        return item.title.indexOf(searchString) === -1;
      });
      if (matchCategory || nonItemMatches) {
        newData.push(group);
        return
      }
      // If the title of an item contains the string of the search filter,
      // this item and its category should be displayed.
      var groupCustomItems = angular.extend({}, group);
      groupCustomItems.items = [];
      group.items.forEach(function(item) {
        if (item.title.indexOf(searchString) > -1) {
          groupCustomItems.items.push(item);
        }
      });
      if (groupCustomItems.items.length) {
        newData.push(groupCustomItems);
      }
    });
    return newData;
  };
});

View

<input type="text" ng-model="filterText" />
  <ul>
    <li ng-repeat="group in data | customFilter:filterText">
      {{ group.category }}
      <ul>
        <li ng-repeat="item in group.items">
          {{ item.title }} 
        </li>
      </ul>
  </ul>

I had to assume your data was wrong, because it didn't match your html view

You might want to read filter documentation

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you very much! That is exactly what I wanted. Many thanks for taking the time.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.