< Previous chapterNext chapter >
Last updated .
The previous chapter showed how to display a collection using ng-repeat. Oftentimes, there is a need to display only a subset of the collection items. The filter criteria for this may be static or dynamic. If they are static, they can be specified in the directive in markup. If they are dynamic, one option is to implement them with a filter function in the controller. In the next example, I will enable the user to search contacts by entering part of a name in a search input box. The listed contacts are then limited to only those contacts whose names start with the search phrase.
First, I need a property in the controller representing the search phrase entered by the user. I can then bind that property to an input box in the markup with a ng-model directive. I named the property "SearchPhrase". I also need a predicate function to determine if an item should be included in the listing or not. The function receives the current item (a "Contact" object) as its first argument. Here is the script for the controller with the new property and filter function:
( // app.js
function ()
{
var app = angular.module('AngularProject', []);
app.controller("myController", MyController);
function MyController()
{
var vm = this;
// Array of Contact objects
vm.Contacts =
[
{
Name: "Albert Einstein",
Email: "alei@princeton.org",
},
{
Name: "Niels Bohr",
Email: "nibo@nbinstitute.ku.dk",
},
{
Name: "Stephen Hawking",
Email: "shaw@cambridge.org",
},
];
vm.SearchPhrase = "";
vm.SearchByName =
function (item, index, array)
{
if (vm.SearchPhrase === "")
return true;
return item.Name.substring(0, vm.SearchPhrase.length).toUpperCase() === vm.SearchPhrase.toUpperCase();
};
}
}
)();
I added the search input box to the template (i.e. the markup), and now I must also define the filter. An Angular filter expression takes the form | filter:expression, where expression can be a string, object or function. In this example it is the function just added to the controller, so I can add a filter to the ng-repeat directive as such:
<!doctype html>
<html ng-app="AngularProject">
<head>
<title>Home</title>
<meta charset="utf-8" />
<script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script>
</head>
<body ng-controller="myController as vm">
Name: <input ng-model="vm.SearchPhrase"/>
<table>
<thead><tr><td>Name</td><td>Email</td></tr></thead>
<tbody>
<tr ng-repeat="contact in vm.Contacts | filter:vm.SearchByName">
<td>{{ contact.Name }}</td>
<td>{{ contact.Email }}</td>
</tr>
</tbody>
</table>
<script src="Scripts/App.js"></script>
</body>
</html>
Initially, the search input is empty, which causes all items to be displayed. But any time the user changes the text in the input box, the search result - the table - is updated. This is because every time the user types in the search field, the "SearchPhrase" property changes - remember it is bound with an ng-model directive. And when a controller property changes, Angular responds by reevaluating the filter.
Apart from filtering, another common requirement is that of ordering. In Angular, ordering takes the same form as filtering. In fact, technically, ordering is a form of filtering. To illustrate ordering of the items in the example contact list, I added a BirthDate property to the objects in the vm.Contacts array, producing this modified version of the script:
( // app.js
function ()
{
var app = angular.module('AngularProject', []);
app.controller("myController", MyController);
function MyController()
{
var vm = this;
// Array of Contact objects
vm.Contacts =
[
{
Name: "Albert Einstein",
Email: "alei@princeton.org",
BirthDate: new Date(1879, 3, 14),
},
{
Name: "Niels Bohr",
Email: "nibo@nbinstitute.ku.dk",
BirthDate: new Date(1885, 10, 7),
},
{
Name: "Stephen Hawking",
Email: "shaw@cambridge.org",
BirthDate: new Date(1942, 1, 8),
},
];
vm.SearchPhrase = "";
vm.SearchByName =
function (item, index, array)
{
if (vm.SearchPhrase === "")
return true;
return item.Name.substring(0, vm.SearchPhrase.length).toUpperCase() === vm.SearchPhrase.toUpperCase();
};
}
}
)();
I want to sort the items by BirthDate, which is a date, and to do that in descending order. This is accomplished by adding an orderBy clause in the ng-repeat directive:
<!doctype html>
<html ng-app="AngularProject">
<head>
<title>Home</title>
<meta charset="utf-8" />
<script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script>
</head>
<body ng-controller="myController as vm">
Name: <input ng-model="vm.SearchPhrase"/>
<table>
<thead><tr><td>Name</td><td>Email</td></tr></thead>
<tbody>
<tr ng-repeat="contact in vm.Contacts | filter:vm.SearchByName | orderBy:'-BirthDate'">
<td>{{ contact.Name }}</td>
<td>{{ contact.Email }}</td>
<td>{{ contact.BirthDate }}</td>
</tr>
</tbody>
</table>
<script src="Scripts/App.js"></script>
</body>
</html>
As the example shows, multiple filters can be applied, separated by '|' characters. In this case, the ordering is by BirthDate, and the '-' prefix causes it to be in descending order. Leave out the leading '-' and the order is ascending. This is what it looks like in the browser:
If you inspect the browser display, you will notice that the ordering is by date, but textually; the youngest contact is at the top, contrary to the expected. It turns out that this is because I I wrote orderBy:-'BirthDate' in the orderBy clause. The single quotes cause the expression to be evaluated as a string. Leaving out the single quotes (orderBy:-BirthDate) fixes it:
There is one glaring fault with the list, however: The date column shows the complete date, including the time parts. It is silly to give someone's birthday with an accuracy down to the millisecond. Solving this requires some formatting of the output.
I want to show the dates in the list properly without the time part. I took the opportunity to add a number property to the contact objects, just to check out the formatting of numbers. This is the updated script file:
Scripts/app.js:
( // app.js
function ()
{
var app = angular.module('AngularProject', []);
app.controller("myController", MyController);
function MyController()
{
var vm = this;
// Array of Contact objects
vm.Contacts =
[
{
Name: "Albert Einstein",
Email: "alei@princeton.org",
BirthDate: new Date(1879, 3, 14),
SomeNumber: 789.2,
},
{
Name: "Niels Bohr",
Email: "nibo@nbinstitute.ku.dk",
BirthDate: new Date(1885, 10, 7),
SomeNumber: 1242.23493,
},
{
Name: "Stephen Hawking",
Email: "shaw@cambridge.org",
BirthDate: new Date(1942, 1, 8),
SomeNumber: null
},
];
vm.SearchPhrase = "";
vm.SearchByName =
function (item, index, array)
{
if (vm.SearchPhrase === "")
return true;
return item.Name.substring(0, vm.SearchPhrase.length).toUpperCase() === vm.SearchPhrase.toUpperCase();
};
}
}
)();
Formatting is done with the same syntax as filtering and ordering - again, in technical terms, formatting is filtering. There are many ways and variations of formatting in Angular, the example below illustrates just two ways of formatting a date and a number:
<!doctype html>
<html ng-app="AngularProject">
<head>
<title>Home</title>
<meta charset="utf-8" />
<script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script>
</head>
<body ng-controller="myController as vm">
Name: <input ng-model="vm.SearchPhrase"/>
<table>
<thead><tr><td>Name</td><td>Email</td><td>BirthDate</td><td>SomeNumber</td></tr></thead>
<tbody>
<tr ng-repeat="contact in vm.Contacts | filter:vm.SearchByName | orderBy:-BirthDate">
<td>{{ contact.Name }}</td>
<td>{{ contact.Email }}</td>
<td>{{ contact.BirthDate | date:'shortDate' }}</td>
<td>{{ contact.SomeNumber | number:3 }}</td>
</tr>
</tbody>
</table>
<script src="Scripts/App.js"></script>
</body>
</html>
In this example, the date is formatted as a short date, i.e. giving the date parts as numbers and with no time. The number format string specifies that numbers should be shown with three decimals. Angular rounds up or down, as is evident from checking the result in the browser:
Unfortunately, there is still something wrong with the result I see in the browser: Dates and times are formatted by American standards. Living in Denmark and running a Danish version of Windows, I actually expected the output to respect the current locale, but no. In order to make Angular honour a specific locale, you have to reference a script file with settings for that specific locale, like this:
<!doctype html>
<html ng-app="AngularProject">
<head>
<title>Home</title>
<meta charset="utf-8" />
<script src="Scripts/Angular/angular-1.5.0-rc.0/angular.js"></script>
<script src="Scripts/Angular/angular-1.5.0-rc.0/i18n/angular-locale_da-dk.js"></script>
</head>
<body ng-controller="myController as vm">
Name: <input ng-model="vm.SearchPhrase"/>
<table>
<thead><tr><td>Name</td><td>Email</td><td>BirthDate</td><td>SomeNumber</td></tr></thead>
<tbody>
<tr ng-repeat="contact in vm.Contacts | filter:vm.SearchByName | orderBy:-BirthDate">
<td>{{ contact.Name }}</td>
<td>{{ contact.Email }}</td>
<td>{{ contact.BirthDate | date:'shortDate' }}</td>
<td>{{ contact.SomeNumber | number:3 }}</td>
</tr>
</tbody>
</table>
<script src="Scripts/App.js"></script>
</body>
</html>
This references the Angular script file for the Danish locale. I did not have to download or install anything - the script file was already in the Angular.js package I downloaded at the outset of this document series. Now, the display is almost right. The number formatting is fine with ',' as the decimal symbol and the date formatting is also much better, though not entirely right (the date "14/04/1879" should be "14-04-1879"):
Having Angular respect what happens to be my current locale is nice, but what if the person viewing your page is from Казахстан? It seems there is no built-in way to get automatic formatting respecting the current locale of the user, but there are workarounds.
filter: The filter can be used to filter the contents of a collection. The syntax is | filter : expression, where "expression" can be a string, an object or a function.
orderBy: The orderBy 'filter' can be used to sort the contents of a collection. The syntax is | orderBy : expression, where "expression" can be a string, a function or an array of functions or strings.
date: This 'filter' is used to format a date or datetime. The syntax is | date : format, where "format" is a string.
number: This 'filter' is used to format a number. The syntax is | number : fractionSize, where "fractionSize" is a number specifying the number of decimal places.
< Previous chapterNext : Services and dependency injection >
2016 by Niels Hede Pedersen