Reputation: 9184
In my angularjs app i get via json such example of data:
{"id":"1a", "name": "aaa", "emails": {{"[email protected]"}, {"[email protected]"}}},
{"id":"2a", "name": "aba", "emails": {{"[email protected]"}, {"[email protected]"}}},
{"id":"3a", "name": "aab", "emails": {{"[email protected]"}, {"[email protected]"}}},
and for performance reasons i didn't use filter for ng-repeat, but use ng-show schema...
so in controller i have such code (search is my inputs value):
$scope.$watch('search', function(newVal, oldVal) {
$scope.filteredArray = $filter('filter')($scope.users, $scope.search, newVal);
});
but it search even in id's, for example i enter a and get it from id, but i do not need field id here...
So how to search with filter only in specific fields?
Upvotes: 4
Views: 4375
Reputation: 382
If you can change your field name to something starting with $, then Angular will skip this field in filtering. Compare https://next.plnkr.co/edit/TjSdGICBfcR8WDyV with original example from https://docs.angularjs.org/api/ng/filter/filter. I added $ to name
and now | filter
skips field $name
in global filtering using search.$
.
Upvotes: 0
Reputation: 8789
If you care about performance, probably the best choice would be not to use $filter('filter')
at all. Assuming your data is a JSON you can do the following:
function contains(src, value, except) {
var key;
switch(typeof src) {
case 'string':
case 'number':
case 'boolean':
return String(src).indexOf(value) > -1;
case 'object':
except = except || [];
for(key in src) {
if( src.hasOwnProperty(key) &&
except.indexOf(key) < 0 &&
contains(src[key], value, except)
) {
return true;
}
}
}
return false;
}
angular.module('app', []).
factory('appService', function() {
var data = [
{"id":"1a", "name": "aaa", "emails": ["[email protected]", "[email protected]"]},
{"id":"2a", "name": "aba", "emails": ["[email protected]", "[email protected]"]},
{"id":"3a", "name": "aab", "emails": ["[email protected]", "[email protected]", ["[email protected]", "[email protected]"]]}
];
return {
getFilteredData: function(filter, except) {
return filter ? data.filter(function(item) {
return contains(item, filter, except)
}) : data.slice(0);
}
}
}).
controller('appController', ['$scope', 'appService', function($scope, appService) {
$scope.search = '';
$scope.$watch('search', function(val) {
$scope.data = appService.getFilteredData(val, ['id']);
});
}]);
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://code.angularjs.org/1.4.0-beta.6/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="appController">
<input ng-model="search" ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"/>
<ul>
<li ng-repeat="item in data">{{::item}}</li>
</ul>
</body>
</html>
Code above will make a deep search in all fields of data and it's items excluding list of fields passed as parameter (in this example ['id']
)
Tip #1: Implementing business logic inside controller is kind of bad practice in Angular, which has services for that purpose.
Tip #2: If you have large data-sets to be displayed, debouncing filter change will be very beneficial from performance point of view.
Tip #3: If your data-set items are immutable, you can take advantage of one-time binding (using {{::item}}
syntax) and speed-up your app by reducing number of watches created by Angular.
Upvotes: 6
Reputation: 5242
Several answers have been given already, and great tips were given. My contribution will not summarise the work of others, but just mention an alternate way to filter data with angularjs, with performance in mind: using an extremely fast, third-party filtering tool.
Yes, having filters in the ng-repeat with x in data | myFilter
is great for readability, but performs poorly with a large collection, as the filter is evaluated repeatedly.
With large collections, it is best to trade readability for performance, and filter in the controller, like mentioned in the OP.
Now if you are going to filter in the controller for performance's sake, you might as well not re-invent the wheel, and use existing filtering tools.
Here is a plunker with a simple toolbox, filtering instantly a collection of a half-million documents.
I wrote it several months ago, as the filtering of a collection of 3000+ documents was slowing down my app when I used either custom or built-in angularjs filters.
filter a large collection using an extremely fast third-party tool: crossFilter
Now enter a number in the box: the filtering is (almost) instant. In fact, the reason it is not instant is because I introduced a 750ms delay on purpose, to give time to the user to type, and not filter at every key press:
var filterTextTimeout=750;
EDIT: as rightly mentioned in the comments, with angularjs versions >1.2, the debounce
option of ng-model
should be used instead of $timeout
The filtering tool used is crossFilter, along with ng-crossfilter.
The reason it can be so fast is that it creates (very quickly) its own sorted indexes.
It comes up with many features, like RegExp, InArray Filter, fuzzy filters, map-reduce, etc...
Upvotes: 2
Reputation: 54524
You can customize the filter expression by calling filter function on each field you are interested in.
angular.module("app", []).controller("ctrl", ["$scope", "$filter", function ($scope, $filter) {
$scope.users = [{
"id": "1a",
"name": "aaa",
"emails": ["[email protected]", "[email protected]"]
}, {
"id": "2a",
"name": "aba",
"emails": ["[email protected]", "[email protected]"]
}, {
"id": "3a",
"name": "aab",
"emails": ["[email protected]", "[email protected]"]
}];
$scope.$watch('search', function (newVal, oldVal) {
//use function to implement a specific expression
var expression = function (value, index) {
fields = ['name', 'emails'];
for (var i in fields) {
field = fields[i];
query = {}; //create an expression
query[field] = $scope.search;
r = $filter('filter')([value], query);
if (r.length > 0) return true;
}
return false;
}
$scope.filteredArray = $filter('filter')($scope.users, expression)
});
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">Search:
<input type="text" ng-model="search">
<div ng-repeat="x in filteredArray">{{x}}</div>
</div>
</div>
Upvotes: 1
Reputation: 20129
The comparator for the filter can be an object that mirrors the objects in your array. If this is the case, it will only filter based on the exiting properties. That is to say if newVal
is an object of the following form:
{
name: someVal,
emails: otherVal
}
You will only filter on name
and emails
, not id
. Having an id
field which is blank is also fine (that just means you wont search by id
).
Here is an example:
angular.module("app", []).controller("ctrl", ["$scope", "$filter", function($scope, $filter){
$scope.data = [
{"id":"1a", "name": "aaa", "emails": ["[email protected]", "[email protected]"]},
{"id":"2a", "name": "aba", "emails": ["[email protected]", "[email protected]"]},
{"id":"3a", "name": "aab", "emails": ["[email protected]", "[email protected]"]}
];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="ctrl">
Name: <input type="text" ng-model="filter.name"><br>
Email: <input type="text" ng-model="filter.emails">
<div ng-repeat="x in data | filter:filter">
{{x}}
</div>
</div>
</div>
Note how entering a value in the emails input will search the array properly. if you want you can also set both inputs to the same model, and then set that model to filter.name
and filter.email
.
//html
Search Query: <input type="text" ng-model="query">
//js
$scope.$watch('query', function(newVal, oldVal){
$scope.filter.name=newVal;
$scope.filter.emails=newVal;
}
Upvotes: -1