Given a controller with a $scope property that is an object with other properties rather than an array like below, how should I filter the ng-repeat
set?
I would change my data structure to an array. Anyway, here's another implementation to filter your friends object.
angular.module('filters',['utils'])
.filter('friendFilter', function(utils){
return function(input, query){
if(!query) return input;
var result = [];
angular.forEach(input, function(friend){
if(utils.compareStr(friend.name, query) ||
utils.compareStr(friend.phone, query))
result.push(friend);
});
return result;
};
});
This iterates over the object only once, compares by name
and phone
and can be called like this.
<li ng-repeat="friend in friends | friendFilter:query">
I defined the compareStr
in another module, but you don't really need to do it.
angular.module('utils', [])
.factory('utils', function(){
return{
compareStr: function(stra, strb){
stra = ("" + stra).toLowerCase();
strb = ("" + strb).toLowerCase();
return stra.indexOf(strb) !== -1;
}
};
});
Don't forget to inject the filters
module into your app
angular.module('app',['filters'])
Here's the full example: http://jsbin.com/acagag/5/edit
I guess you can't do it directly with 'filter'. Looking at the code in angular.js, these are the first lines of the filter function:
function filterFilter() {
return function(array, expression) {
if (!(array instanceof Array)) return array;
So if it receives something different from an array, it does nothing.
Here is one way to do it, not sure if I would recommend it, but it's is an idea:
In the controller, just convert to an array before passing it to the filter:
$scope.filteredFriends = function() {
var array = [];
for(key in $scope.friends) {
array.push($scope.friends[key]);
}
return $filter('filter')(array, $scope.query);
}
And the ng-repeat:
<li ng-repeat="friend in filteredFriends()">
Example: http://jsbin.com/acagag/2/edit
Maybe a better solution is to write a custom filter.
I made an example on how to not change your object to an Array. I think this answers the question more correct.
I had the same problem that i could not search in a Object.
http://jsbin.com/acagag/223/edit
angular.module('filters',['utils'])
.filter('friendFilter', function(utils){
return function(input, query){
if(!query) return input;
var result = {};
angular.forEach(input, function(friendData, friend){
if(utils.compareStr(friend, query) ||
utils.compareStr(friendData.phone, query))
result[friend] = friendData;
});
return result;
};
});
So just instead of returning a array you return a object.
Hope this helps someone!
Rather than using a filter you can also simply :
$http.get('api/users').success(function(data){
angular.forEach(data, function(value, key){
users.push(value);
});
$scope.users = users;
});
And in the template
<input type="text" ng-model="query" placeholder="Search user"/>
<li ng-repeat="user in users | filter:query">
{{ user.name }}
</li>
I had the same problem, and succeeded by creating my own filter that accepts a map, while still using the built-in filter for doing the actual matching.
My custom filter traverses the map, and for each element calls the built-in filter, but given that that filter only accepts an array, I wrap each element in an array of length 1 (the [data] below). So the match succeeds if the output-array's length is still 1.
.filter('mapFilter', function($filter) {
var filter = $filter('filter');
return function(map, expression, comparator) {
if (! expression) return map;
var result = {};
angular.forEach(map, function(data, index) {
if (filter([data], expression, comparator).length)
result[index] = data;
});
return result;
}
})
This setup loses efficiency of course because the built-in filter is required to determine what it needs to do for each element in the map, instead of only once if you give it an array with all elements, but in my case, with a map of 500 elements the filtering is still instanteanous.
Not an optimal solution but if you want something quick and dirty :
<li ng-repeat="(id, friend) in friends | filter:query" ng-hide="id !== query.id">
<span>{{friend.name}} @ {{friend.phone}}</span>
</li>
Or ng-if
instead of ng-hide