Software Development

AngularJS: Different ways of using Array Filters

AngularJS provides 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 ‘date’ filter to format a Date value into human readable Date representation like MM-DD-YYYY as {{dob | date}}.

On the other hand there are Array filtering feature which is very useful while filtering data from an Array of JavaScript objects. The Array filtering is very commonly used with a Table along with
ng-repeat directive.

For example, we can have a list of Todos which we can display in a Table using ng-repeat tag. And we can have a text field to search todos which matches any one of the data properties of 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 ng-repeat attribute. As you type in the search input field, the $scope.todos array will be filtered and only matching records will be shown up. This is a “match anything” type filter, means the search criteria will be checked against all properties (id, title, description, date) of Todo object.

If you want to search only on one field, say ‘description‘, you can apply filter as follows:

<tr ng-repeat="todo in todos| filter: {description: searchTodos}">

If you want to display only Todos which aren’t done yet then you can do it as follows:

<tr ng-repeat="todo in todos| filter: {description: searchTodos, done: false}">

Note that here the 2 conditions will be applied using AND conditions. 

If you want to display only Todos which aren’t done yet and you want to search on all fields not just on ‘description‘ then you can do it as follows:

<tr ng-repeat="todo in todos| filter: {$: searchTodos, done: false}">

Here $ means all fields. So far so good as it is a simple and straight forward case.

How about having nested objects in our Array objects and we want to search based on a nested object property?

Let’s look at such a type of 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, CreditCard etc with the current balance details.
  • I will have a list of Payees such as HouseRent, PowerBill, Salary etc which fall into INCOME or EXPENDITURE categories.
  • I will record all my transactions by picking one of the account 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 wise. 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">
<br/>
 <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 list of transactions in a table. Observe that the transactions contains 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 lets look at them case by case.

Case#1: Search by Payee Name 

In our transaction object we have a nested payee object which contains name property on which we want to perform search.

Let us create form which will contain all our filters before transactions table.

The first thought that came to my mind to perform a search on a nested property is use the nested property path in filter as follows:

<input type="text" ng-model="payeeName">
...
<tr ng-repeat="txn in transactions| filter: {payee.name : payeeName}">

But THIS WONT 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 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 have bind the input field ng-model to “filterTxn.payee.name” and used filter: filterTxn as filter. So txn.payee.name will be matched against filterTxn.payee.name.

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 $scope.accounts array by displaying Account Name and using id as value.

The key part here is we have 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 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” is option is selected no account filter will be applied.

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 need for this!

Case#4: Search by Payees of Expenditure type 

Aaah..this is interesting! We want to search by Payee names but only in EXPENDITURE type payees.

We can’t simply apply filter like “filter: expPayeeFilter | filter: {txnType: ‘EXPENDITURE’}” because it will always filter by EXPENDITURE.

So we will create a custom filter to perform “search by payee name in EXPENDITURE type payees only when some filter text 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, 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.

We have seen few interesting options on how to use AngularJS array filtering features.

Hope it helps!

Siva Reddy

Katamreddy Siva Prasad is a Senior Software Engineer working in E-Commerce domain. His areas of interest include Object Oriented Design, SOLID Design principles, RESTful WebServices and OpenSource softwares including Spring, MyBatis and Jenkins.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

10 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jasa Pembuatan Aplikasi

Great tutorial, I want to ask how to make a search by the payee automatically filter only by initial letters are suitable displayed?
For example, when I type the letter “i” in the box “search by payee” show only “InternetBill” without “PowerBill”

Please help me.

Siva
9 years ago

Hi,
The default filter uses “contains” filter and you are looking for “startsWith” filter.
You can create a custom “startsWith” kind of filter.

Please refer: http://stackoverflow.com/questions/19501300/angular-js-startswith-custom-filter

Jasa Aplikasi Android
9 years ago
Reply to  Siva

I am confused how to apply it in your code, what should I change? I am a beginner angularjs.

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;
};
}]);

Siva
9 years ago

Try changing
if(item.payee.name.toLowerCase().indexOf(searchCriteria.toLowerCase()) != -1)
to
if(item.payee.name.toLowerCase().indexOf(searchCriteria.toLowerCase()) == 0)

Jasa Pembuatan Aplikasi
Reply to  Siva

Hi Siva, I’ve tried it, but it did not work. please help me

Siva
9 years ago

Hi,
“SearchCriteria” is bound to “Search by Expenditure Payees” input. So try entering your startWith filter value in that field and you can see the filtering works properly.

Similarly you can create another custom filter for “Search by Payee” input field “input1”.

sandeep
sandeep
9 years ago

Hello;
I have used multi select in view like:

and in my controller

angular.forEach( $scope.xs, function( value, key ) {
if ( value.checked === true ) {
// Do your stuff here
$scope.xs.push({name:value.name,age:value.age})
}
});
//Save() here

I want to display the saved multiselect data

pls help me ……
my codes are….
————————

name:{{x}}

———-
its displying [object object]

pls do help…..

Stefan
Stefan
8 years ago

Hey,

I like search for type. But how can I get the filter values dynamicaly by angular code?

Thanks for help
Stefan

Utkarsh
Utkarsh
8 years ago

Amazing tutorial.Helped a lot.

Indhumathi Gopalan
Indhumathi Gopalan
8 years ago

great tutorial. very neat explanation.

Back to top button