Reputation: 1254
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
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
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
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