
The days of users waiting for page reloads when filtering and ordering information are long gone. With the increased capability of browsers to run more complex code, JS applications are becoming a key part of most website builds. AngularJS is a powerful application framework that makes large applications easier to create.
The data and presentation layers are clearly separated and easy to manage. With its own HTTP request methods and a cut-down version of jQuery built in (jqLite), AngularJS provides all the tools needed for web app development.
We will cover the app setup, retrieval of data from web services, binding data to your HTML and then filtering it for the user. All Controllers, Services and Filters are created within Angular modules, making each module reusable and the app easy for other developers to extend. It is easier to manage these modules as separate files but it is recommended that these files are bundled together in a live environment, reducing HTTP requests for the app.
Set up the HTML
Start by adding a reference to AngularJS from the Google CDN to your HTML page. Then, add a reference to your application on the body tag using an attribute as shown. If you use the ‘data-ng-’ prefix for your Angular attributes as opposed to ‘ng-’, your HTML will be valid.
001 <!DOCTYPE html> 002 <html lang=”en” xmlns=”http://www.w3.org/1999/xhtml”> 003 <head> 004 <meta charset=”utf-8” /> 005 <title>Angular Listing App</title> 006 <script src=”http://ajax.googleapis.com/ajax/libs/ angularjs/1.2.15/angular.min.js”></script> 007 </head> 008 <body data-ng-app=”listingApp”> 009 </body> 010 </html>
Create the scripts
In your scripts folder, create a folder for modules. Then create a file named ‘myApp.js’ in the scripts folder and a file named ‘listing.js’ in the modules folder – be sure to reference these in your HTML page. Add the following code to these files; this defines your core application and uses dependency injection to add your module to this application.
001 //modules/listing.js 002 angular.module(‘listing.module’,[]) 003 //myApp.js 004 var myApp = angular.module (‘listingApp’, [‘listing.module’]);
Add a controller
Define a controller in listing.js and pass in $scope – all data and methods defined in this controller will extend $scope. Add a reference to your new controller in your HTML page and you can then display any data defined in $scope. In a browser window you should now see the $scope.title value displayed in a h1 tag.
001 <!DOCTYPE html>
002 <html lang=”en” xmlns=”http://www.w3.org/1999/xhtml”>
003 <head>
004 <meta charset=”utf-8” />
005 <title>Angular Listing App</title>
006 <script src=”http://ajax.googleapis.com/ajax/libs/
angularjs/1.2.15/angular.min.js”></script>
007 <script src=”scripts/modules/listing.js”></script>
008 <script src=”scripts/myApp.js”></script>
009 </head>
010 <body data-ng-app=”listingApp”>
011 <section data-ng-controller=”listingCtrl”>
012 <h1>{{title}}</h1>
013 </section>
014 </body>
015 </html>
016 //modules/listing.js
017 angular.module(‘listing.module’, [])
018 .controller(‘listingCtrl’, [‘$scope’, function
($scope) {
019 “use strict”;
020 $scope.title = “Technology News”;
021 }]);
022
Create a data service
Create a services folder and a file named ‘listingServices.js’. In this file you can create a services module with a data service providing GET and POST methods using Angular’s $http methods. These GET and POST methods accept a callback function to pass the retrieved data to.
001 angular.module(‘listing.services’, [])
002 .service(‘data’, [‘$http’, function ($http) {
003 “use strict”
004 this.get = function (url, callback) {
005 $http({ method: ‘GET’, url: url }).
006 success(function (data, status, headers, config) {
007 // this callback will be called asynchronously
008 // when the response is available
009 callback(data);
000 }).
011 error(function (data, status, headers, config) {
012 // called asynchronously if an error occurs
013 // or server returns response with an error
status.
014 throw “No data returned from “ + url;
015 });
016 };
017 this.post = function (url, callback, obj) {
018 $http({ method: ‘POST’, url: url, data: obj }).
019 success(function (data, status, headers, config) {
020 // this callback will be called asynchronously
021 // when the response is available
022 callback(data);
023 }).
024 error(function (data, status, headers, config) {
025 // called asynchronously if an error occurs
026 // or server returns response with an error
status.
027 throw “No data returned from “ + url;
028 });
029 };
030 }]);
Create some test data
Now we have a service to make HTTP requests, we need some data. If you are working with a web service that is already available to you then you can use this. In this tutorial we will be using some test JSON data as a separate file, created using www.jsoneditoronline.org with the following structure.
001 {
002 “articles”: [
003 {
004 “title”: “Science behind the D-Day landings”,
005 “description”: “How innovations in science and
engineering made the D-Day landings possible
with giant troop tanks and tanks that could
drive on water.”,
006 “pubDate”: “2014-02-14”,
007 “tags”: [
008 “tag1”,
009 “tag4”
010 ]
011 },
012 {
013 “title”: “...”,
014 “description”: “...”,
015 “pubDate”: “...”
016 “tags”: [
017 ...
018 ]
019 }
020 ]
021 }
Add your service
To use the methods within your service you need to add the reference to your service script in your HTML page and inject this module and its methods into your controller as shown. You can pass a URL and callback function to the ‘data.get’ method. You should now see the data logged out in your browser console.
001 angular.module(‘listing.module’, [‘listing.services’])
002 .controller(‘listingCtrl’, [‘$scope’,’data’, function
($scope, data) {
003 ‘use strict’;
004 $scope.title = “Technology News”;
005 $scope.setData = function (data) {
006 $scope.articles = data.articles;
007 console.log($scope.articles);
008 }
009 data.get(‘articles.json’, $scope.setData)
010 }]);
Render your data
Now the data is stored within the controller $scope it can be displayed within the HTML page. For an array of items a repeater can be used that will render the HTML element bound for each item in the data set. Properties held within each data item are also available to be rendered as shown with the ‘title’ and ‘description’ properties.
001 <ul>
002 <li data-ng-repeat=”article in articles”>
003 <h2>{{article.title}}</h2>
004 <p>{{article.description}}</p>
005 </li>
006 </ul>
Handling dates
Dates can be stored in a range of formats and can be converted within an Angular binding. Simply add ‘| date’ after your property reference and your chosen format option eg article.pubDate | date:’m/d/yy’. You can see the range of format options available at docs.angularjs.org/api/ng/filter/date.
001 <ul>
002 <li data-ng-repeat=”article in articles”>
003 <h2>{{article.title}}</h2>
004 <p>{{article.pubDate | date:’dd/MM/yy’}}</p>
005 <p>{{article.description}}</p>
006 </li>
007 </ul>
008 //modules/listing.js
009 $scope.viewLimit = 4;
Ordering and Limiting
To order the listing, an ‘orderBy’ filter can be added to the repeater; this can be followed by ‘:true’ to reverse this ordering. To control the number of items shown in the list a ‘limitTo’ filter can be set. The value for limitTo must be a number and we can store this in our controller as $scope.viewLimit.
001 <ul>
002 <li data-ng-repeat=”article in articles |
orderBy:’pubDate’:true | limitTo: viewLimit”>
003 <h2>{{article.title}}</h2>
004 <p>{{article.pubDate | date:’dd/MM/yy’}}</p>
005 <p>{{article.description}}</p>
006 </li>
007 </ul>
View more
Now add a button after your listing labelled ‘View more’. Use the Angular click binding to increase the $scope.viewLimit value and display more articles. In this example the number will be incremented by eight each time. The Angular hide binding is also used to hide the button; if all data items are displayed, it expects an expression returning a boolean value.
001 <button data-ng-click=”viewMore(8)” data-ng-
hide=”viewLimit >= articles.length”>view more</button>
002 //modules/listing.js
003 $scope.viewMore = function(num){
004 $scope.viewLimit += num;
005 }
Add a sort option
As the listing is already ordered, it is simple to provide a select drop-down for the user to adjust the ordering. The Angular model binding can be used to bind any HTML element to an item in the model or $scope. Create $scope.descending in your listing module and then build a select drop-down bound to it. Now replace the ‘true’ value for reversing the orderBy filter with a reference to this new property.
001 <div class=”controls”>
002 <label for=”sortBy”>Sort by</label>
003 <select id=”sortBy” data-ng-model=”descending”>
004 <option value=”true”>Newest first</option>
005 <option value=”false”>Oldest first</option>
006 </select>
007 </div>
008 <ul>
009 <li data-ng-repeat=”article in articles |
orderBy:’pubDate’:descending | limitTo: viewLimit”>
010 <h2>{{article.title}}</h2>
011 <p>{{article.pubDate | date:’dd/MM/yy’}}</p>
012 <p>{{article.description}}</p>
013 </li>
014 </ul>
015 //modules/listing.js
016 $scope.descending = true;
017
Simple text filter
Add a text input to your page with an Angular model binding. Then, add this new model property as a filter on your repeater. As there is no need to set a default, this model binding doesn’t have to be explicitly created in the $scope. This will update the listed items as the text field is typed, matching by string.
001 <div class=”controls”>
002 <input type=”text” data-ng-model=”query” placeholder
=”search news” />
003 <label for=”sortBy”>Sort by</label>
004 <select id=”sortBy” data-ng-model=”descending”>
005 <option value=”true”>Newest first</option>
006 <option value=”false”>Oldest first</option>
007 </select>
008 </div>
009 <ul>
010 <li data-ng-repeat=”article in articles |
filter: query | orderBy:’pubDate’:descending |
limitTo: viewLimit”>
011 <h2>{{article.title}}</h2>
012 <p>{{article.pubDate | date:’dd/MM/yy’}}</p>
013 <p>{{article.description}}</p>
014 </li>
015 </ul>
Custom filtering
A custom filter can be created as a module and injected into your listing module. This receives the array of items and a filter object and returns a new filtered array. Create a new folder named ‘filters’ and a new file named listingFilters.js with an Angular module as shown.
001 angular.module(‘listing.filters’, [])
002 .filter(‘newsFilter’, function () {
003 return function (items, filters) {
004 }
005 });
006 //modules/listing.js
007 angular.module(‘listing.module’, [‘listing.services’,
‘listing.filters’])...
Use angular.forEach
Now the angular.forEach method can be used to iterate over the data items to be filtered. Add this method, passing in the items and change the ‘filter’ on your listing repeater to ‘newsFilter’. If you log out each item you can check this is running correctly before building your filters object.
001 <ul>
002 <li data-ng-repeat=”article in articles
| newsFilter: filters | orderBy:’pubDate’:descending
| limitTo: viewLimit”>
003 <h2>{{article.title}}</h2>
004 <p>{{article.pubDate | date:’dd/MM/yy’}}</p>
005 <p>{{article.description}}</p>
006 </li>
007 </ul>
008 //filters/listingFilters.js
009 angular.module(‘listing.filters’, [])
010 .filter(‘newsFilter’, function () {
011 return function (items, filters) {
012 angular.forEach(items, function (item) {
013 console.log(item);
014 });
015 return items;
016 }
017 });
018
Create some filters
In your controller $scope create a filters object to include your search text property (remember to change the reference to ‘query’ in your HTML page) and an array of tags. Each tag should have label[string] and selected[boolean] properties. You can then render a checkbox for each tag in your HTML page, as shown below.
001 <input type=”text” data-ng-model=”filters.
query” placeholder=”search news” />
002 <div class=”clear”>
003 <div data-ng-repeat=”tag in filters.
tags” class=”checkbox”>
004 <label for=”{{tag.label}}”>{{tag.label}}</label>
005 <input id=”{{tag.label}}” type=”checkbox” data-ng
-checked=”tag.selected” />
006 </div>
007 </div>
008 //modules/listing.js
009 $scope.filters = {
010 query: “”,
011 tags: [
012 {
013 label: “tag1”,
014 selected: false
015 },
016 {
017 label: “tag2”,
018 selected: false
019 },
020 {
021 label: “tag3”,
022 selected: false
021 },
023 {
024 label: “tag4”,
025 selected: false
026 }
027 ]
028 }
Filter for text
In the filter module now add a check against filter.query. Start with an empty array named ‘filtered’, then check each item for the search text defined. If the item matches, add it to the filtered array., then return the filtered array.
Filter for tags
Extend the filter function to check through all selected tags and compare against the tags of each item. This filter can be extended to support any data structure or range of filters you need. Just make sure that the filters are all held within the filters object passed to this function and you cater for any empty or null values.
Fix the view more button
Once the filtering is implemented you may notice that the ‘View more’ button does not get hidden when fewer items match the filters. This is because it is bound to the total number of items. Instead, you can access the total number of filtered items by creating a new ‘filtered’ variable within your HTML.
001 <ul>
002 <li data-ng-repeat=”article in filtered =
(articles | newsFilter: filters) | orderBy:
’pubDate’:descending | limitTo: viewLimit”>
003 <h2>{{article.title}}</h2>
004 <p>{{article.pubDate | date:’dd/MM/yy’}}</p>
005 <p>{{article.description}}</p>
006 </li>
007 </ul>
008 <button class=”btn” data-ng-click=”viewMore(8)”
data-ng-hide=”viewLimit >= filtered.length”>view
more</button>
Results numbering
Now the total number of filtered items and total number of items can be displayed to the user by simply adding a text element above the listing. This is a common requirement for filtered listings.
001 <p>{{filtered.length}} articles of {{articles.length}}
match your search.</p>
Use multiple controllers
In many applications you will need to share Properties in the $scope of one controller with another. As $scope relates to a specific controller you need to create $rootScope to share properties and inject it into each controller. Create a run function on your controller and add a test property to $rootScope.
001 .run([‘$rootScope’, function ($rootScope) {
002 $rootScope.testValue = “I am in rootScope”;
003 }]);
Share $rootScope
Now you can share $rootScope by injecting it into each controller you need to. As well as providing access to anything defined within $rootScope, it can also be extended from any controller. Make a new controller in your listing module and add this new controller into your HTML page.
001 <section data-ng-controller=”testCtrl”>
002 <h2>{{dataFromRoot}}</h2>
003 </section>
004 //modules/listing.js
005 .controller(‘testCtrl’, [‘$scope’, ‘$rootScope’,
function ($scope, $rootScope) {
006 $scope.dataFromRoot = $rootScope.testValue;
007 }])
008 .run([‘$rootScope’, function ($rootScope) {
009 $rootScope.testValue = “I am in rootScope”;
010 }]);
011
Utilise $watch
If you have a property or dataset being changed within $rootScope you can use the $watch method to listen for that change and then act upon it. This provides a vital connection across multiple controllers and modules, allowing you to build much more complex applications.
001 $rootScope.$watch(‘testData’, function(newValue,
oldValue){
002 // $rootScope.testData has been updated
003 });
004
