AngularJS: Different Ways of Using Array Filters
In this article, we learn how to utilize AngularJS's array filter features in a variety of use cases. Read on to get started.
Join the DZone community and get the full member experience.
Join For FreeAngularJS provides a filter feature which can be used to format input value or to filter an array with the given matching input criteria. For example, you can use the 'date' filter to format a date value into a human-readable date representation like MM-DD-YYYY as {{dob | date}}.
On the other hand, there are array filtering features that are very useful while filtering data from an array of JavaScript objects. Array filtering is commonly used with a table and an ng-repeat
directive.
For example, we can have a list of Todos that we display in a table using the ng-repeat
tag. And we can have a text field to search todos that match any one of the data properties of the todo
object as follows:
$scope.todos = [
{id: 1,title: 'Learn AngularJS', description: 'Learn AngularJS', done: true, date: new Date()} ,
{id: 2,title: 'Explore ui-router', description: 'Explore and use ui-router instead of ngRoute', done: true, date: new Date()} ,
{id: 3,title: 'Play with Restangular', description: 'Restangular seems better than $resource, have a look', done: false, date: new Date()} ,
{id: 4,title: 'Try yeoman', description: 'No more labour work..use Yeoman', done: false, date: new Date()} ,
{id: 5,title: 'Try MEANJS', description: 'Aah..MEANJS stack seems cool..why dont u try once', done: false, date: new Date()}
];
<input type="text" ng-model="searchTodos">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Description</th>
<th>Done?</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="todo in todos| filter: searchTodos">
<td>{{$index + 1}}</td>
<td>{{todo.title}}</td>
<td>{{todo.description}}</td>
<td>{{todo.done}}</td>
<td>{{todo.date | date}}</td>
</tr>
</tbody>
</table>
Observe that our search input field's ng-model
attribute is set to 'searchTodos
' which we have used to filter on the ng-repeat
attribute. As you type in the search input field, the $scope.todos
array will be filtered and only matching records will show up. This is a "match anything" type filter, which means the search criteria will be checked against all properties (id, title, description, date) of the todo
object.
If you want to search only one field, like 'description
', you can apply a filter as follows:
<tr ng-repeat="todo in todos| filter: {description: searchTodos}">
If you want to only display Todos which aren't done yet then you can do as follows:
<tr ng-repeat="todo in todos| filter: {description: searchTodos, done: false}">
Note that the two conditions will be applied using AND conditions.
If you want to display only Todos that aren't done yet and you want to search on all fields, not just on 'description
,' then you can do it like this:
<tr ng-repeat="todo in todos| filter: {$: searchTodos, done: false}">
Here $
means all fields. So far so good. It's a simple and straightforward case.
How about having nested objects in our array objects that we want to search based on a nested object property?
Use Cases
Let's look at such a scenario. In order to explain these scenarios, I am using some code examples from my ebuddy application.
In my ebuddy application, I have an ExpenseManager module where I will keep track of my expenses as follows:
- I will have a list of accounts such as Cash, Savings Bank Account, credit card, etc., with the current balance details.
- I will have a list of payees such as rent, Power Bill, Salary, etc., which fall into INCOME or EXPENDITURE categories.
- I will record all my transactions by picking one of the accounts and a payee and the amount.
This application is just to record my financial transactions so that I can see monthly reports by account or payee. I hope you get an idea about the domain model.
Now let us create a simple AngularJS application and set some sample data.
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>My AngularJS App</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.20/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.2.0/js/bootstrap.min.js"></script>
<script>
var myApp = angular.module('myApp',[]);
myApp.controller('SampleController', function($scope){
$scope.accounts = [
{id: 1, name: 'Cash'},
{id: 2, name: 'Bank Savings'}
];
$scope.payees = [
{id:'1',name:'HouseRent', txnType:'EXPENDITURE'},
{id: '2', name:'InternetBill', txnType:'EXPENDITURE'},
{id:'3', name: 'PowerBill', txnType:'EXPENDITURE'},
{id:'4', name: 'Salary', txnType:'INCOME'}
];
$scope.transactions = [
{id:'1', txnType:'EXPENDITURE', amount: 1000, account: $scope.accounts[0], payee: $scope.payees[0]},
{id:'2', txnType:'EXPENDITURE', amount: 500, account: $scope.accounts[1], payee: $scope.payees[1]},
{id:'3', txnType:'EXPENDITURE', amount: 1200, account: $scope.accounts[0], payee: $scope.payees[1]},
{id:'4', txnType:'INCOME', amount: 5000, account: $scope.accounts[1], payee: $scope.payees[3]},
{id:'5', txnType:'EXPENDITURE', amount:200, account: $scope.accounts[0], payee: $scope.payees[2]}
];
});
</script>
</head>
<body ng-controller="SampleController">
<div class="col-md-8 col-md-offset-2">
<h3>Transaction Details</h3>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>#</th>
<th>Account</th>
<th>Type</th>
<th>Payee</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="txn in transactions">
<td>{{$index + 1}}</td>
<td>{{txn.account.name}}</td>
<td>{{txn.txnType}}</td>
<td>{{txn.payee.name}}</td>
<td>{{txn.amount}}</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>
This is a very simple AngularJS page which is displaying a list of transactions in a table. Observe that the transactions contain nested objects (account
, payee
) and we are displaying nested properties (txn.account.name
, txn.payee.name
) in our table.
Now, we want to filter the transactions in a variety of ways, so let's look at them, case by case.
Use Case #1: Search by Payee Name
In our transaction object, we have a nested payee
object which contains the name
property on which we want to perform a search.
Let's create a form which will contain all our filters before the transactions table.
The first thought that came to my mind was to perform a search on a nested property in use in the nested property path in the filter as follows:
<input type="text" ng-model="payeeName">
...
<tr ng-repeat="txn in transactions| filter: {payee.name : payeeName}">
But THIS WON'T WORK.
To search on a nested property we can name our input field ng-model
to match the target property path and use the root object name as a filter, as follows:
<div class="col-md-8 col-md-offset-2">
<form class="form-horizontal" role="form">
<div class="form-group">
<label for="input1" class="col-sm-4 control-label">Search by Payee</label>
<div class="col-sm-6">
<input type="text" class="form-control" id="input1" placeholder="Payee Name" ng-model="filterTxn.payee.name">
</div>
</div>
<!-- additional filters will come here -->
</form>
<h3>Transaction Details</h3>
<table class="table table-striped table-bordered">
...
<tbody>
<tr ng-repeat="txn in transactions| filter: filterTxn">
...
...
</tr>
</tbody>
</table>
</div>
Observe that we bound the input field ng-model
to "filterTxn.payee.name
" and used the filter, filterTxn
, as a filter. So txn.payee.name
will be matched against filterTxn.payee.name
.
Use Case #2: Filter by Accounts Dropdown
We would like to filter the transactions by using Accounts Select dropdown. First, we need to populate a select dropdown using $scope.accounts
and use it as a filter.
Add the following filter after our first filter.
<div class="form-group">
<label for="input2" class="col-sm-4 control-label">Search By Account</label>
<div class="col-sm-6">
<select id="input2" class="form-control" ng-model="filterTxn.account">
<option value="">All Accounts</option>
<option ng-repeat="item in accounts" value="{{item.id}}">{{item.name}}</option>
</select>
</div>
</div>
Here we are populating a <select> field with the $scope.accounts
array by displaying the Account Name and using the id as a value.
The key part here is we have to bind ng-model
to filterTxn.account
. When we select an account, the selected account object reference will be stored in filterTxn.account
. As we already have filterTxn
as a filter, the account filter will also be applied along with payee name filter.
Also note that the first option "All Accounts" value is empty ("") which will be treated as null by AngularJS, so when the "All Accounts" option is selected no account filter will be applied.
Use Case #3: Search by Transaction Type
We want to filter the transaction by transaction type (INCOME or EXPENDITURE):
Add the following filter after the second filter:
<div class="form-group">
<label for="input3" class="col-sm-4 control-label">Search By Type</label>
<div class="col-sm-6">
<select id="input3" class="form-control" ng-model="filterTxn.txnType">
<option value="">All Types</option>
<option value="EXPENDITURE">EXPENDITURE</option>
<option value="INCOME">INCOME</option>
</select>
</div>
</div>
I hope no further explanation is needed for this one!
Use Case #4: Search by Payees of Expenditure Type
Ah, this is interesting! We want to search by Payee names but only in the EXPENDITURE
type.
We can't simply apply a filter like filter: expPayeeFilter | filter: {txnType: 'EXPENDITURE'}
because it will always filter by EXPENDITURE
.
So we will create a custom filter to perform a search by payee
name in the EXPENDITURE
type only when some filtered text is entered as follows:
myApp.filter('expenditurePayeeFilter', [function($filter) {
return function(inputArray, searchCriteria, txnType){
if(!angular.isDefined(searchCriteria) || searchCriteria == ''){
return inputArray;
}
var data=[];
angular.forEach(inputArray, function(item){
if(item.txnType == txnType){
if(item.payee.name.toLowerCase().indexOf(searchCriteria.toLowerCase()) != -1){
data.push(item);
}
}
});
return data;
};
}]);
We have created a custom filter using myApp.filter()
and inside it we have used angular.forEach()
to iterate over the input array. The rest is plain JavaScript... no magic.
Now we will apply this custom filter as follows:
<tr ng-repeat="txn in transactions| filter: filterTxn | expenditurePayeeFilter:searchCriteria:'EXPENDITURE'">
<td>{{$index + 1}}</td>
<td>{{txn.account.name}}</td>
<td>{{txn.txnType}}</td>
<td>{{txn.payee.name}}</td>
<td>{{txn.amount}}</td>
</tr>
Observer the syntax: customFilterName:param1:param2:..:paramN
.
These parameters will be passed as arguments to the function inside our custom directive.
Conclusion
We have seen a few interesting options on how to use the AngularJS array filtering features.
You can find the complete code at https://gist.github.com/sivaprasadreddy/fbee047803d14631fafd
Hope this helps!
If you enjoyed this article and want to learn more about Angular, check out our compendium of tutorials and articles from JS to 8.
Published at DZone with permission of Siva Prasad Reddy Katamreddy, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments