sandboxj
sandboxj

Reputation: 1254

Angular filter multiple strings for objects

I have an array (projects) in JSON form. The object project has the fields:

"name":"pname",
"id":"1",
"keywords":"aab bb cc"

I display these objects in div tags using

<div ng-repeat="p in projects | filter : searchText">
   {{p.name}}, {{p.id}} etc.
</div>

I connected the filter to a input form ng-model="searchText" and want to use multiple strings to search. For example searching: "pname aa bb" should result in the above project being shown.

I know I have to use custom filters and split the searchText. But the approach that works to filter string arrays does not seem to work for JSON objects. What is the difference?

Attempt for simple string array e.g. ['aab', 'bb', 'cc'] that did not work for the object array:

.filter("myFilter", function(){
return function(input, searchText){
    var returnArray = [];
    var searchTextSplit = searchText.toLowerCase().split(' ');
    for(var x = 0; x < input.length; x++){
        var count = 0;
        for(var y = 0; y < searchTextSplit.length; y++){
            if(input[x].toLowerCase().indexOf(searchTextSplit[y]) !== -1){
                count++;
            }
        }
        if(count == searchTextSplit.length){
             returnArray.push(input[x]);   
        }
    }
    return returnArray;
}
});

How do I make it work for objects?

Upvotes: 2

Views: 2227

Answers (3)

miqh
miqh

Reputation: 3664

But the approach that works to filter string arrays does not seem to work for JSON objects. What is the difference?

When the expression argument for the filter filter is a string, Angular will attempt to match the given string with only one specific key-value pair if the items being matched across are of the object type.

For reference, from the Angular documentation:

The string is used for matching against the contents of the array. All strings or objects with string properties in array that match this string will be returned. This also applies to nested object properties.

The behaviour you appear to want is to match a string with values across multiple key-value pairs.

That said, you can take advantage of the fact the expression argument can be passed a predicate function to introduce smarter matching behaviour.

See my following Plunker for a working solution. It can be further optimised or adjusted to handle more cases, namely dealing with array values.

One point to note about my solution is that it supports matching terms even if your object has nested properties.

Upvotes: 2

Engin
Engin

Reputation: 815

You can simply use the $filter function of angular inside of your custom filter.

app.filter('myFilter', ['$filter', function($filter){
    return function(data, text){
        var textArr = text.split(' ');
        angular.forEach(textArr, function(test){
            if(test){
                data = $filter('filter')(data, test);
            }
        });
        return data;
    }
}]);

Then pass the searchText as parameter to your custom filter e.g.

<li ng-repeat="obj in objects | myFilter:searchText">{{obj.name}}</li>

Upvotes: 0

logee
logee

Reputation: 5067

See plunker. You can join all the property values and then use regex to match all the search terms. I've added a boundary condition i.e. it matches the beginning of each keyword. You can modify it if you don't want to do boundary matching.

So if you type pname aa bb ss the searchText you will get your object because it matches ALL the terms. But `pname aa zz' will not return your object.

app.filter('myFilter', ['$filter', function($filter) {
    var buildMatchingString = function(object) {
        var matchingString = '';
        // Join all the property values into 1 string separated by a 'space'
        // so that we can do boundary matching
        angular.forEach(object, function(prop, key) {
            matchingString += ((prop || '') + ' ');
        });
        return matchingString;
    };

    return function(list, searchText) {
        if (!searchText) {
          // Return everything if there is no user input
            return list;
        }
        var result = [];
        // Split all the search terms
        var keywords = searchText.split(' ');

        return $filter('filter')(list, function(elem) {
            var matchingString = buildMatchingString(elem);
            // Check that the properties of the object matches the
            // beginning of ALL the search terms
            for(var i = 0; i < keywords.length; i++) {
                if (!matchingString.match(new RegExp('\\b' + keywords[i] + '.*', 'gi'))) {
                    return false;
                }           
            }
            return true;
        });
    };
}]);

Upvotes: 1

Related Questions