Testing

webTiger Logo Wide

Using AngularJS

This article provides an introduction to AngularJS, how to get started, and the programming structure to follow. Update: It is worth noting this was written for AngularJS, and the framework has moved on and the newer version is called simply ‘Angular’, and maintained independently. (This article is being retained for historical reasons.)

UPDATE: support for AngularJS has now ended (as of January 2022). A new Angular framework was released to replace it.

In this article:

Recommended File Structure

Before embarking on development using AngularJS, it’s worth quickly paying lip-service to file/folder structure. The tutorials on the official AngularJS website provide guidelines on a suggested layout. This is summarised below:

{root}/
  css/
    bootstrap.min.css
    site.css
  js/
    angular/* /* AngularJS framework script files, etc. */
    bootstrap/* /* Bootstrap framework script files, etc. */
    jquery/* /* jQuery script files, etc. */
  app/
    appName/ /* Module-specific folder. */
      appName.js /* An app definition (the module.) */
      appName.entityType.name.js /* AngularJS entity for the 'appName' module. */
      appName.entityType.name.tests.js /* Unit tests for the entity. */
      submodule-name/
        subModuleName.js /* A sub-module 'appName' is dependent on. */
        subModuleName.entityType.name.js /* An entity for the sub-module. */
        subModuleName.entityType.name.test.js /* Unit tests for entity. */Code language: plaintext (plaintext)

For example:

{root}/css/bootstrap.min.css
{root}/css/site.css
{root}/js/angular/*
{root}/js/bootstrap/*
{root}/js/jquery/*
{root}/js/app/orders/orders.js
{root}/js/app/orders/orders.controller.orderdetails.js
{root}/js/app/orders/orders.controller.orderdetails.tests.js
{root}/js/app/orders/orders.component.custdetails.js
{root}/js/app/orders/orders.component.custdetails.html
{root}/js/app/orders/orders.component.custdetails.tests.js
{root}/js/app/orders/orders.component.itemsordered.js
{root}/js/app/orders/orders.component.itemsordered.html
{root}/js/app/orders/orders.component.itemsordered.tests.jsCode language: plaintext (plaintext)

Even in the case of a large, sprawling web app, this structure should remain manageable.

An obvious disadvantage of this approach for smaller projects is a bloated head region in web pages, since individual JS files need to be referenced, and there’s unnecessary nesting in the JS folder structure if you are only authoring a couple of script files. For production projects you are likely to be bundling (with gulp or webpack for example) so it may not matter. If not (e.g. in an internal hosting environment, for company internal apps) use best judgement to decide the best folder structure and approach to file content for a particular project, also bearing in mind how the app might grow.

(Back to Top)

The App

AngularJS targets functionality within an ‘app’. There can be more than one app on a web page, but only one app can be specified declaratively within a web page – when using multiple apps, you must register them programmatically via JavaScript. AngularJS refers to the linking of an app to a region in a web page as ‘bootstrapping’, but this shouldn’t be confused with the Bootstrap API (and just to make things more confusing, AngularJS relies on both Bootstrap and jQuery APIs to function correctly.)

All Web pages employing AngularJS need jQuery, Bootstrap CSS/JS and AngularJS references in the web page (link/script tags.) Ideally these should be placed in the header region of the page, but they can be inserted in the page body immediately before the first ‘app’ region if that is preferred for some reason.

<html>
  <head>
    <link rel="stylesheet" href="css/bootstrap.min.css" />
    <link rel="stylesheet" href="css/bootstrap-theme.min.css" /> <!-- optional -->
    <script src="js/jquery/jquery.min.js"></script>
    <script src="js/bootstrap/bootstrap.min.js"></script>
    <script src="js/angular/angular.min.js"></script>
    <!-- Additional per-app scripts need to be defined here! For example... -->
    <script src="js/app/myapp.js"></script>
    <script src="js/app/myModule/myapp.myModule.component.someComponent.js"></script>
  </head>
</html>Code language: HTML, XML (xml)

The ‘ng-app’ attribute is used to decorate the root element that Angular should consider the scope of its application. It can be specified at the root HTML tag, or anywhere else within a web page (such as on a particular div tag.) Alternatively, Angular can be bootstrapped programmatically instead (as previously mentioned)…

<script>
    angular
        .module('myApp', [])
        .controller('myController', ['$scope', function ($scope) {
            $scope.sayHello = 'World';
      }]);

    angular.element(document).ready(function() {
        angular.bootstrap(document, ['myApp']);
    });
</script>Code language: JavaScript (javascript)

Remember: you can only have one app declaratively-bootstrapped in a Web document, but you can programmatically initialise more than one!

(Back to Top)

Basic Concepts

There are a number of key concepts that AngularJS has been built upon.

Basic Concepts – Templated Views

The first concept being introduced is the idea of the ‘template’. This is a normal HTML file/page but with additional markup to cater for dynamic layout. Adding script tags that reference AngularJS in a web page loads handlers that identify these additional attributes, tags and syntax and then interpret and respond to them.

AngularJS recommends development using the MVC (model, view, controller) design pattern – defining models to represent data, views that can display data, and controllers that provide the business logic to decide how/what data (from a model) should be displayed in a view. Data binding is the term often used to describe the synchronisation of data between a model and a view that a controller performs. In the simple example that follows, the model is defined declaratively in the ng-init attribute, the view is the extended HTML markup defining the layout (i.e. the template), and the controller is implicitly defined by the variables encapsulated in double-brace pairs: {{variable}}.

<div ng-app ng-init="product='blank disk';quantity=3;unitPrice=2.99">
    <b>Invoice:</b>
    <table>
        <thead>
            <tr>
                <th>Product</th>
                <th>Quantity</th>
                <th>Unit Price</th>
                <th>Sub-Total</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td><span>{{product}}</span></td>
                <td><span>{{quantity}}</span></td>
                <td><span>{{unitPrice}}</span></td>
                <td><span>{{quantity * unitPrice}}</td>
            </tr>
        </tbody>
    </table>
    <br />
    <div>
        <b>Total: {{quantity * unitPrice}}</b>
    </div>
</div>Code language: HTML, XML (xml)

Basic Concepts – Dependency Injection

AngularJS is heavily built upon the premise of dependency injection. This design pattern is based on the dependency inversion principle that turns normal dependency behaviour on its head – instead of a high-level component being dependent on a particular low level component, the relationship is abstracted such that both high-level and low-level components only need to comply with a defined contract. The high-level component is developed to consume capabilities defined in the contract, and then relies on some mechanism to provide a specific low-level implementation of the contract when needed – this is the injection part. This concept of abstraction, and injection of dependency objects, allows code to be written much more generically, deferring knowledge of specific objects until the point of use.

Dependency injection is also particularly useful when testing code. For example, you could call the function you want to test, passing in a test-version of one or more of the dependency objects to stimulate specific behaviour and confirm outcomes are as expected.

NOTE: within the scope of dependency injection, dependencies (or dependency objects) are often referred to as ‘services’, and the targets that these services are injected into referred to as ‘clients’. AngularJS defines a ‘service’ as reusable business logic independent of views, so it is important to appreciate the differentiation here.

Rather than using the ‘app’ as an implicit controller, like we did in our invoice example above, it is much better to define controller logic explicitly.

angular.module('invoicing', [])
    .controller('invoiceController', function invoiceController() {
        this.product = 'blank disk';
        this.quantity = 3;
        this.unitPrice = 2.99;
        this.total = this.quantity * this.unitPrice;
    });Code language: JavaScript (javascript)
<div ng-app ng-controller="invoiceController as invoice">
    <b>Invoice:</b>
    <table>
        <thead>
            <tr>
                <th>Product</th>
                <th>Quantity</th>
                <th>Unit Price (£)</th>
                <th>Sub-Total</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td><span>{{invoice.product}}</span></td>
                <td><span>{{invoice.quantity}}</span></td>
                <td><span>{{invoice.unitPrice}}</span></td>
                <td><span>{{invoice.total}}</td>
            </tr>
        </tbody>
    </table>
    <br />
    <div>
        <b>Total: £{{invoice.total}}</b>
    </div>
</div>Code language: HTML, XML (xml)

Notice we can alias the controller name for better readability. (Alternatively the ‘as invoice’ fragment could have been omitted and we could have just referenced ‘product’, ‘quantity’, etc. as before without any qualifier.)

The above ‘invoiceController’ example demonstrates dependency injection in action. Think of the HTML fragment (the template) as the app we are injecting a controller into. Providing the contract (i.e. variable names) remains the same we could replace the ng-controller=”…” attribute value with another compatible controller and the view would still be updated, but via the alternative controller.

Basic Concepts – Services

Services, in AngularJS, are reusable business logic and are defined as ‘factory’ entities. These can be defined centrally and consumed by multiple controllers, etc. Better still, stub definitions for services can initially be defined and then those signatures used to develop controllers, etc. before the service itself has been fully developed. This means a development team can do some up-front design work defining the contracts that will be used (the function stubs) and then develop different parts of the solution in parallel, testing their own piece by injecting mock objects until the proper service code is available.

Services are defined within a module, controller, filter, directive, etc. as necessary. The example below demonstrates the approach, defining shared functionality in a separate ‘corePopups’ module.

/* Define some alert popups in one module. */
angular.module('corePopups', [])
    .factory('popups', function() {
        var boo = function() {
            alert('Boo!');
        };

        return { scareUser: boo };
    });

/* Using shared services in a controller */
angular.module('popupsApp', ['corePopups'])
    .controller('popupsController', ['$scope', 'popups', function($scope, popups) {
        $scope.scareUser = function() { popups.scareUser(); };
    }]);Code language: JavaScript (javascript)
<div ng-app="popupsApp" ng-controller="popupsController">
    <button ng-click="scareUser()">Scare User</button>
</div>Code language: HTML, XML (xml)

NOTE: Services shouldn’t attempt to reference the ‘$scope’ – only controllers should do that. If a service is defined with a dependency on $scope then Angular is likely to raise an error something like “Unknown provider: $scopeProvider <- $scope”.

Basic Concepts – Directives

Directives are identifiers in templated HTML markup that inform the Angular compiler to attach specific behaviour to an element. Having read through the post this far, you’ll have observed directives without even realising what they are. For example: ng-app, ng-repeat, ng-model, ng-click, and ng-controller are all directives.

Recommended best practice is to define and use directives as element tags or attributes. Directives can also be defined using the class attribute but this isn’t recommended.

<!-- Attribute-based directives. -->
<!-- (ng-model and ng-click are built-in AngularJS directives.) -->
<input ng-model="type" />&nbsp;<button ng-click="changeType(type)">Update</button>

<!-- Element-based directive. -->
<!-- The customer element is defined programmatically in JavaScript as a special type of directive - a 'component'. -->
<customer>{{name}}</customer>

<!-- This is also valid, but not recommended. -->
<input class="ng-model: type;" />Code language: HTML, XML (xml)

Name normalisation is a technique Angular uses to identify directives. In the background JavaScript, directives will be named ngApp, ngRepeat, ngModel, ngBind, etc. but declaratively they will be defined as ng-app, x-ng-repeat, data-ng-model, ng:bind, etc. It is important to understand this because when we develop our own directives they will not have kebab case names in the JavaScript form – but those kebab case names are likely to be specified in the markup.

During normalisation processing, the name will be stripped of any x- or data- prefix, and then any hyphens, colons, underscores, etc. will be removed. Case insensitive matching will then be attempted on the directive name. NOTE: With the exception of the ‘data-‘ form (which is allowed to support HTML validation tools, etc.), all modified declarative forms of the directive name should be considered deprecated and not for use any more. The preferred declarative naming convention is now the short-form of ng-app, ng-model, etc.

Directives are created at the Angular ‘module’ level. It is recommended that you prefix all your directive names with a few unique characters to clearly identify them and improve readability. Prefixing in this way may also reduce the possibility of collisions with new element tag names introduced in future versions of the HTML spec. The example below defines a directive for a particular controller.

var myApp = angular.module('exApp', []);
myApp
    .controller('UserDetailsController', ['$scope', function($scope) {
        $scope.user = {
            name: 'Danielle Smith',
            username: 'dsmith2',
            userId: 782195
        };
    }])
    .directive('exUserDetails', function() {
        /* (ex is our prefix, and the full attribute name will be ex-user-details.) */
        return {
            template: '<table><tbody>' +
                '<tr><td>User ID:</td><td>{{user.userId}}</td></tr>' +
                '<tr><td>Full Name:</td><td>{{user.name}}</td></tr>' +
                '<tr><td>Username:</td><td>{{user.username}}</td></tr>' +
                '</tbody></table>'
            };
    });Code language: JavaScript (javascript)
<div ng-app="exApp" ng-controller="UserDetailsController">
    <div ex-user-details></div>
</div>Code language: HTML, XML (xml)

Directives are a fairly complicated area, so we’ll go into more detail in the Directives in More Detail section later in the article.

Basic Concepts – Components

Components are a special type of directive with a simpler configuration and certain limitations (e.g. components can only be defined in markup by using their element tag.)

An immediate difference between directives and components lies in their relative definitions. Components are not defined using factory functions like directives are, and instead they define their configuration only.

angular.module('mApp', []).component('myComponent', {
        templateUrl: 'myComponentTemplate.html',
        controller: MyComponentController,
        bindings: { data: '<' }
    });Code language: JavaScript (javascript)

The options the configuration supports are based on the $compile service. More information about this is provided in the Directives in More Detail section later in the article.

A key difference between directives and components is scope – components don’t have them. Instead, components are bound to a controller and that defines the scope. This is because components should never attempt to modify data or the DOM if it is outside their own scope.

Bindings should be defined based on purpose, such that inputs and outputs are clearly identified. The use of two-way bindings (using ‘=’) should be discouraged. You can use the <, @ and & identifiers with the bindings option to specify inputs (<, @) and outputs (&) respectively. This is important because components should not affect parent scopes as a general rule, as previously mentioned. Also, objects that are modified in the component scope may affect the parent scope since both will be referencing the same object. That being the case, objects passed as inputs to a component should not be directly modified.

Basic Concepts – Filters

Filters provide a means to format data before it is displayed to the user in the template. The pipe character ‘|’ is used to identify use of a filter. AngularJS includes a set of built-in filters (‘filter’, ‘currency’, ‘number’, ‘date’, ‘json’, ‘lowercase’, ‘uppercase’, ‘limitTo’ and ‘orderBy’) and additional bespoke filters can be defined if required. Filters can also be chained together (e.g. value|filter1|filter2) so that the outcome of the first filter is supplied as input to the second filter, etc. so a cascaded format can be produced.

<span id="currency-custom">{{unitPrice*quantity | currency:"£":2}}</span>Code language: HTML, XML (xml)

In the above example, the total price will be formatted with a prefixed UK Pound character and limited to 2 decimal places (pence). If the trailing number was 0 instead of 2, then the value as whole pounds without any decimal part would be displayed.

Custom filters are defined programmatically, as you would expect. For example, here is a filter that accepts two arguments (with markup that calls the filter, with param1 = quote, param2 = final):

/* For an existing app, called 'myApp', that is already defined... */
angular.module('myApp')
    .filter('myFilter', ['param1','param2', function(param1, param2) {
        /* format the data, returning the formatted version! */
    }]);Code language: JavaScript (javascript)
<div ng-app="myApp" ng-controller="myFilterController">
{{someVariable|myFilter:'quote':'final'}}
</div>Code language: HTML, XML (xml)

(Back to Top)

Declarative Syntax

Declarative Syntax – Expressions

Data wrapped in double brace character-pairs are considered placeholder expressions that will be replaced by AngularJS. For example:

{{someVariable}} or {{unitPrice*quantity}}Code language: plaintext (plaintext)

Expressions can be defined in the body of HTML elements, or within attributes.

The syntax isn’t that different from JavaScript except that expressions are defined ‘in context’ (i.e. in the context of a controller.) This means that expressions do not have access to window, document, location, etc. – if use of these global entities is required, the code should be moved into an appropriate function in a controller . Think of expressions as the contents of a JavaScript function, but try to keep things simple. It is better to move complex behaviour/computation into a dedicated function within the controller, rather than stuffing hard to read (and debug) expression syntax into the markup file. The example below (in the next topic, relating to use of built-in directives) demonstrates use of expressions.

Expressions also offer a one-time binding option where the expression is only evaluated the first time it is rendered. This can be useful in scenarios where a value is only going to be set once as it is removed from the expression watch loop and can improve performance. A double colon prefix indicates a one-time binding, e.g. {{::someVariable}}.

Some directives may expose services that can be used in expressions (e.g. event based directives such as ‘ng-keydown’, ‘ng-mouseover’, etc. expose an $event service that returns an instance of jQuery’s event object for the current event that is executing.)

Conditional branch evaluations, with the exception of the ternary operator (condition ? true : false), are not allowed in AngularJS expressions. This relates back to the earlier statement about managing complexity in the markup file syntax.

Declarative Syntax – Using Built-in Directives

Dynamic web pages can be created quickly and easily in markup with just a few AngularJS directives. For example, the ng-repeat directive can be specified as an attribute and used with an expression (iterator) to create a table:

<table class="table">
    <thead>
        <tr>
            <th>&nbsp;</th>
            <th ng-repeat="c in [1,2,3,4,5,6,7,8]">C{{c}}</th>
        </tr>
    </thead>
    <tbody>
        <tr ng-repeat="r in [1,2,3,4]">
            <td>R{{r}}</td>
            <td ng-repeat="c in [1,2,3,4,5,6,7,8]">{{r}},{{c}}</td>
        </tr>
</table>Code language: HTML, XML (xml)

The above HTML fragment would display a table with 9 columns and 5 rows (including title column/row). The matrix position of each cell in the table will be displayed as x,y.

Another useful AngularJS directive is the ‘ng-attr’ attribute. This allows developers to get around potential conflicts with protected attribute names within the DOM, such as style, class, etc. By prefixing the attribute name with ng-attr- (e.g. ng-attr-style=”normal”), the DOM’s built-in ‘style’ attribute conflict is bypassed and the attribute value can be interpreted and processed correctly by AngularJS.

(Back to Top)

The Majority is JavaScript

Most of your AngularJS app is going to be defined in script files. This section provides brief overviews on defining the different types of entity in a typical AngularJS app.

JavaScript – Defining The App Itself

An ‘app’ is defined using:

angular.module('appName', []);

// This is also valid...
var myApp = angular.module('appName', []);Code language: JavaScript (javascript)

When defining the app (actually a ‘module’), the 2nd parameter must be provided. The ‘appName’ literal is the name of your module and can be anything you like, but it’s good practice to name it something suitably descriptive (salesOrders, invoicing, expenses, etc.) The module can also be associated with a variable to make it easier to reference, like in the second example in the code fragment above. The 2nd parameter may used to declare any dependencies the app may have. For example, the following example informs AngularJS that the module has a dependency on the Angular-Animate module.

angular.module('myApp', ['ngAnimate']);Code language: JavaScript (javascript)

An existing app can be referenced elsewhere (even in another code file) by omitting the 2nd parameter. The code fragment immediately below doesn’t create a new module – it references an existing module, and adds a controller definition to it (in this case):

angular.module('myApp').controller( ... ); // Controller code omitted for brevity.Code language: JavaScript (javascript)

JavaScript – Controllers

Controllers are defined with reference to a particular app (module). They provide the linkage (or control) between the data in the model and the view.

// Basic example...
angular.module('myApp')
    .controller('myController', function() {
        /* what to do?! */
    });

// Or, with inputs...
angular.module('myApp')
    .controller('myController', ['$scope', 'input1', function($scope, input1) {
        /* what to do (with the parameters passed into the controller)?! */
        /* ($scope is a built-in service available to controllers.) */
        $scope.prompt = 'Hello world!';
        $scope.myFunc = function(data) {
        /* some code for myFunc. */
            return 'this came from myFunc: ' + data;
        };
    }]);Code language: JavaScript (javascript)
<div class="panel" ng-app="myApp">
    <div ng-controller="myController">
        {{prompt}}<br />
        <br />
        {{myFunc('Goodbye cruel world... Ahhhh!!!!')}}
    </div>
</div>Code language: HTML, XML (xml)

As you can observe in the example above we have added a controller, called ‘myController’, to the ‘myApp’ module that has already been declared elsewhere (since we split out our elements of a module (controllers, directives, etc.) into separate files.) Our controller exposes a property (prompt) and a function (myFunc). In the HTML markup, use of the controller in the view is simply a matter of referencing it within the appropriate tag attribute and then calling on the property and function using our familiar AngularJS syntax.

You may have noticed that we can pass arguments into our controller functions. In this case we are passing a literal text value from the markup, but this could just have easily been a data value from another control on the web page that caused our controller to a connect to a remote web service, updating some other properties in the scope, and then updating the view as a consequence of that. For example:

angular.module('myApp')
    .controller('myController', ['$scope', function($scope) {
        $scope.enterUsernamePrompt = 'Enter your username:';
        $scope.searchPrompt = 'Search';
        $scope.usernameTitle = 'Username:';
        $scope.roleTitle = 'Role:';
        $scope.fullNameTitle = 'Full Name:';
        $scope.EmailTitle = 'Email:';
        $scope.username = '';
        $scope.userRole = '';
        $scope.fullName = '';
        $scope.email = '';
        $scope.findUser = function(username) {
            var user = {}; // Code to search using a web service omitted.
            $scope.username = user.Username;
            $scope.userRole = user.Role;
            $scope.fullName = user.FriendlyName;
            $scope.email = user.Email;
        };
    });Code language: JavaScript (javascript)
<div class="panel" ng-app="myApp" ng-controller="myController">
    <div class="inputData">
        {{enterUsernamePrompt}}&nbsp;
        <input type="text" ng-model="username" />&nbsp;
        <button type="button" onclick="findUser(username)">{{searchPrompt}}</button>
    </div>
    <div class="responseData">
        <table>
            <tbody>
                <tr>
                    <td><strong>{{usernameTitle}}</strong></td>
                    <td>{{username}}</td>
                </tr>
                <tr>
                    <td><strong>{{roleTitle}}</strong></td>
                    <td>{{userRole}}</td></tr>
                <tr>
                    <td><strong>{{fullNameTitle}}</strong></td>
                    <td>{{fullName}}</td>
                </tr>
                <tr>
                    <td><strong>{{EmailTitle}}</strong></td>
                    <td>{{email}}</td>
                </tr>
            </tbody>
        </table>
    </div>
</div>Code language: HTML, XML (xml)

In the above example, when the user enters a username and hits the ‘Search’ button, the username value in the text box will be passed to the controller’s findUser() function. This will then perform a call to a web service, which returns the user details. Setting the properties of the controller’s scope will cause AngularJS to update them in the markup automatically. NOTE: The code to connect to the web service has been omitted in this case because we don’t have a live web service to connect to!

We have also moved the definitions of all our labels into the controller too. This could be useful if we are providing websites in multiple languages, because the user changing their country or language options could automatically update the display so they can read it. This is obviously simplified by comparison to what coding a real solution might involve (because we’ve just provided static literal values here and would need to look up language-specific string resources in reality.)

At this point it is worth noting that AngularJS developer documentation recommends that controllers be limited to providing the business rules behaviour for a single view. Any generic or re-usable code should be migrated into AngularJS services.

JavaScript – Directives

Directives are covered in detail in the Directives in More Detail section below, so they will only be mentioned in passing here. They are identifiers in templated HTML markup that inform the AngularJS compiler to attach specific behaviours to elements. Directives can be declared as follows:

// Basic example...
angular.module('myApp')
    .directive('dirName', function() {
        /* what to do?! */
    });

// Or, with inputs...
angular
    .module('myApp')
    .directive('displayTime', ['$interval', function($interval) {
        /* Code omitted for brevity. */
    }]);Code language: JavaScript (javascript)
<div ng-app="myApp">
    Time: <display-time></display-time>
</div>Code language: HTML, XML (xml)

In the second JS example above, the ‘$interval’ service is being passed into the directive so that it can register timer based events.

JavaScript – Components

Components are a special type of directive with a simpler configuration and certain limitations. Most notably, they can only be declared in markup using their element tag.

// Basic example...
angular.module('myApp')
    .component('cmpName', function() {
        /* what to do?! */
    });

// Or, with inputs...
angular.module('myApp')
    .component('showTime', ['$interval', function($interval) {
        /* what to do?! */
    }]);Code language: JavaScript (javascript)

Components should be used in favour of directives where possible, but they may not be able to be used in certain scenarios (due to their limitations.) They are not defined using factory functions like directives are, and instead they are defined by their configuration only as previously mentioned. They are also forced to use an isolated scope based on the controller they are attached to. (See the information on scoping in the Directives in More Detail section below.)

Occasions when use of a directive is preferred to that of a component include when you need access to compile functions or pre-link functions, when multiple directives need to be attached to a particular element, or when multi-directive elements need directives to be compiled with specific priorities.

JavaScript – Filters

Filters provide a means to format content before it is displayed in the DOM. AngularJS provides a collection of filters out-of-the-box and custom filters can also be defined where required. Angular’s built-in filters are not being explained in detail here, but the API’s developer’s guide provides comprehensive information on them, like the date filter for example. Defining a custom filter is relatively straightforward:

// Basic example...
angular.module('myApp')
    .filter('myFormat', function() {
        return function() {
            /* formatting code goes here. */
        };
    });

// Or, with inputs...
angular.module('myApp')
    .filter('myFormat', function() {
        return function(param1, param2) {
            /* formatting code goes here. */
        };
    });Code language: JavaScript (javascript)

Here, I’ve purposely avoided declaring a filter with a name of ‘myFilter’, and there’s a good reason for that. In code, you reference the filter with a ‘Filter’ suffix, myFormatFilter in this case, so having a function name of myFilterFilter would look a bit odd!

Parameters can be used with filters to provide more flexibility, as shown in the second example above. This allows filters to be developed such that they are generic with per-use configuration options that could drastically change the formatting behaviour. For example, Angular’s built-in date/time filter could be used to display the date/time in significantly different ways…

{{currentTime | date:'d/M/yy h:mm a'}} <!-- 28/10/2014 7:46 AM (short date and time) -->
{{currentTime | date:'EEEE, d MMMM y'}} <!-- Tuesday, 28 October 2014 (long date format) -->
{{currentTime | date:'HH:mm'}} <!-- 13:07 or 06:58 (time of day, both parts forced to 2 digits) -->
{{currentTime | date:'h:mm a':'+0100'}} <!-- 9:45 AM (time of day, timezone of GMT/UTC + 1 hour) -->Code language: HTML, XML (xml)

JavaScript – Services

Services offer reusable functionality that may be consumed by multiple controllers, etc. They promote good design practice, where shared or generic capability can be centrally defined and consumed where needed. They are declared using the ‘factory’ service function.

// Basic example...
angular.module('myApp')
    .factory('serviceName', function() {
        /* Service behaviours here! */
    });

// Or, with dependencies...
angular.module('myApp')
    .factory('serviceName', ['$interval', function($interval) {
        /* Service behaviours here! */
    }]);Code language: JavaScript (javascript)

We could define a service for displaying modal dialogue prompts to the user, for example. In the code definition below, we are just using JavaScript’s window.alert() function for now but we could later change the service to use Bootstrap or jQuery modal behaviours instead. Everywhere the service was being consumed the behaviours would be updated to use the revised ones automatically without having to go through thousands of lines of code to replace ‘alert’ with the alternative implementation.

// Define a 'popups' service in the 'core' module...
angular.module('core', [])
    .factory('popups', function() {
        var boo = function() {
            alert('Boo!');
        };
        var info = function(message) {
            alert(message);
        };
        
        return { scareUser: boo, show: info };
    });

// Define the app with a dependency on our 'core' module, then we can use the popups service...
angular.module('myApp', ['core'])
    .controller('myController', ['popups', function(popups) {
        $scope.message = '';
        $scope.scare = function() { popups.scareUser(); };
        $scope.showMsg = function() { popups.show($scope.message); };
    }]);Code language: JavaScript (javascript)
<div class="panel" ng-app="myApp">
    <div ng-controller="myController">
        <button ng-click="scare()">Scare Me!</button><br />
        Message:&nbsp;
        <input type="text" ng-model="message" />&nbsp;
        <button ng-click="showMsg(message)">Show Message</button>
    </div>
</div>Code language: HTML, XML (xml)

(Back to Top)

Directives in More Detail

This section builds upon the basic information about directives provided in the Basic Concepts section. If you aren’t familiar with directives, please read about them in that section first.

Here is an example of a directive.

<script>
    var myApp = angular.module('myApp', []);
    myApp.directive('exUserDetails', function() {
        return {
            template: '<table><tbody>' +
                '<tr><td>User ID:</td><td>{{user.userId}}</td></tr>' +
                '<tr><td>Full Name:</td><td>{{user.name}}</td></tr>' +
                '<tr><td>Username:</td><td>{{user.username}}</td></tr>' +
                '</tbody></table>'
            };
        });
</script>
<div ng-controller="MyController">
    <div ex-user-details></div>
</div>Code language: HTML, XML (xml)

Notice that the inline ‘template’ value is a HTML fragment. This is fine for simple template layouts, but as the complexity grows it is better to separate out the ‘view’ into one or more separate HTML files and reference them using the ‘templateUrl’ property. The second example below shows how we can define a directive with dynamic connectivity to multiple HTML template files.

myApp.directive('exUserDetails', function() {
    return { templateUrl: 'myApp.directive.exUserDetails.html' };
});

/* Alternative definition with dynamic template name. */
myApp.directive('exUserDetailsAlt', function() {
    return {
        restrict: 'A',
        scope: { username: '=user' },
        templateUrl: function(element, attr) { 
            'myApp.directive.' + attr.view + '.html';
        };
    });Code language: JavaScript (javascript)
<!-- Full contents of myApp.directive.exUserDetails.html file -->
<table>
    <thead>
        <tr><th>User ID:</th><th>{{user.userId}}</th></tr>
    </thead>
    <tbody>
        <tr>
            <td>Full Name:</td>
            <td>{{user.name}}</td>
        </tr>
        <tr>
            <td>Username:</td>
            <td>{{user.username}}</td>
        </tr>
    </tbody>
</table>Code language: HTML, XML (xml)

The template HTML file only needs to contain the minimum markup that was in the inline example. This is because it will be injected into the larger DOM in exactly the same way. The second (alternative) directive definition provides a function to the templateUrl option. This allows the markup to define the type of view that should be displayed. The example below demonstrates this, with the view=”…” attribute controlling which template file will be rendered.

<div ng-controller="UserDetailsController">
    <div ex-user-details-alt view="id" username="dsmith2"></div><br />
    <div ex-user-details-alt view="username" username="dsmith2"></div><br />
    <div ex-user-details-alt view="id" username="jbloggs"></div><br />
    <div ex-user-details-alt view="username" username="jbloggs"></div>
</div>Code language: HTML, XML (xml)

The built-in ngTransclude directive can be used to specify that the directive tags may have content within them. This is useful when you want to define a template but give the developer some flexibility in what ancillary content is displayed within it. Dialogue boxes are a good example where this may be the case – your directive could define a generic layout and set of behaviours and leave the developer using the directive to define the content within it. For example:

<!-- myApp.directive.popup.html template file -->
<div class="modalBackground">
    <div class="modalContainer">
        <a href class="close" ng-click="hide('cancel')">&times;</a>
        <!-- The part of the template that will accept content. -->
        <div ng-transclude></div>
    </div>
</div>

<!-- markup implementing the directive. -->
<popup class="messageBox">
    <div class="popupTitle">Error</div>
    <div class="popupError">Something went wrong!</div>
    <div class="popupButtons">
        <button type="button" ng-click="hide('ok')">OK</button>
    </div>
</popup>
<button type="button" ng-click="show()">Show Error</button>Code language: HTML, XML (xml)

A number of configuration options exist via Angular’s $compile service. These can further specialise and control use and behaviour of directives. (For example, transclude can be specified as an option in code (transclude: true), as well as declaratively as described above.)

We can restrict the types of declarative use of a directive with the ‘restrict’ option, as we have in the second directive definition above (we’re only allowing attribute usage.) The full set of settings for this option are: A=Attribute, C=Class, E=Element, M=Comments. Values can also be combined as necessary (e.g. ‘AE’ to only allow attribute and element usage – which is actually the default if the option is omitted!)

The ‘require’ option can be used to specify dependencies the directive has. For example, the directive may be dependent on another directive (since they can communicate with one another) or it may be that it needs access to more than one controller. Where multiple dependencies exist (directives, controllers) then an array of names can be declared (e.g. require: [‘first’,’second’]).

The ‘controller’ option can be used to reference a particular controller that will be associated with the directive’s template (so it doesn’t have to be specified in the tag element in the markup file), or it can even be defined inline (e.g. controller: function() { … }).

Binding of data can be further refined using the ‘scope’ option. Let’s say we had a departments list controller with named entries, we can now specify a name attribute in our extended markup to filter our data on. For this to work we’d need to have variables in the controller like $scope.it = { … }, $scope.finance = { … }, etc. A key concept to take away from this is that we can isolate what data in the model (the variables defined in the controller are essentially that) we want the view to have access to. In the users list example we described above, the first two lines would only be able to access the dsmith2 variable in the model, and the last two lines would only be able to access jbloggs.

The ‘link’ option in the $compile service can be used to manipulate the DOM and register listeners to perform regular updates, etc. If the directive options define requirements on other entities (i.e. using the require: ‘myDependency’ option), then these will be provided in the ‘link’ function parameters – and if multiple dependencies exist the additional parameter will represent an array of objects. For example, you could use ‘link’ to update the current time being displayed every second…

<div ng-controller="myTimeController">
    Date format: <input ng-model="format"> <br />
    Current Date/Time: <display-time></display-time>
</div>Code language: HTML, XML (xml)
angular.module('myApp', [])
    .controller('myTimeController', ['$scope', function($scope) {
        $scope.format = '';
    }])
    .directive('displayTime', ['$interval', 'dateFilter', function($interval, dateFilter) {
        function formatListener(scope, element, attrs) {
            var format, timerId;

            function updateTime() {
                element.text(dateFilter(new Date(), format));
            }

            scope.$watch('format', function(value) {
                format = value;
                updateTime();
            });

            element.on('$destroy', function() {
                $interval.cancel(timerId);
            });

            // start the UI update process; save the timerId for canceling
            timerId = $interval(function() {
                updateTime(); // update DOM
            }, 1000);
        }

        return { link: formatListener };
    }]);Code language: JavaScript (javascript)

Notice the use of $watch to monitor the date format input box and update when changes are made. Without this the behaviour may not work correctly in some Web browsers (e.g. all versions of IE, including IE11.)

Animations

Animations are supported by a separate module, the AngularJS Animate module. If you’ve downloaded the full AngularJS distribution then it should already be present. If not then you can reference it via the CDN or download it separately. You simply add a script reference to the animations JS file below the main AngularJS declaration, like so:

<html>
    <head>
        <!-- Other elements omitted for brevity. -->
        <script src="js/angular/angular.js"></script>
        <script src="js/angular/angular-animate.js"></script>
    </head>
</html>Code language: HTML, XML (xml)
<!-- The animations module needs to be referenced as a dependent module of the app... -->
angular.module('myApp', ['ngAnimate']);Code language: JavaScript (javascript)

Various directives built into AngularJS support animations, including: ngClass, ngIf, ngInclude, ngRepeat, ngSwitch, ngView, ngShow and ngHide. The events, etc. that support animations varies by entity. For example ngShow/ngHide directives support add and remove animations, and ngView supports enter and leave animations.

Animation behaviours can be defined declaratively in CSS classes or directly in JavaScript. When using the CSS approach, you can append AngularJS directives to class names to perform the animations. For example:

<style>
    .anim-content { 
        border: 2px solid black; 
        margin-top: 5px; 
        padding: 5px; 
    }
    .anim-hide.ng-hide-add.ng-hide-add-active {
        transition: all linear 1.0s; /* to make transitions work in IE / Firefox */
        -webkit-transition-duration: 1000ms; /* to make transitions work with web-kit (Chrome, etc.) */
        opacity: 0;
    }
</style>
<div id="animContainer" ng-init="displayed=true">
    <span><input type="checkbox" ng-model="displayed" /> Visible</span>
    <div class="anim-content anim-hide" ng-show="displayed">
        Content in the box that fades out!
    </div>
</div>
<script>
    angular.module('myApp', ['ngAnimate']);
    angular.element(animContainer).ready(function() {
        angular.bootstrap(animContainer, ['myApp']);
    });
</script>Code language: HTML, XML (xml)

This will produce a behaviour that fades the content box out when it is being hidden in response to the user unticking the ‘Visible’ check-box. NOTE: This has been written as a one-way animation (fade-out only) to demonstrate how it might be used with content you want to display quickly but like animations when they are being dismissed (e.g. modal dialogue boxes.)

The equivalent could be defined programmatically, like so…

<style>
    .anim-content { 
        border: 2px solid black; 
        margin-top: 5px; 
        padding: 5px; 
    }
</style>
<div id="animContainer" ng-init="displayed=true">
    <span><input type="checkbox" ng-model="displayed" /> Visible</span>
    <div class="anim-content anim-hide" ng-show="displayed">
        Content in the box that fades out!
    </div>
</div>
<script>
    angular.module(myApp', ['ngAnimate']);
    angular.module(myApp')
        .animation('.anim-hide', function() {
            return { beforeAddClass: function(element, className, done) {
                if (className === 'ng-hide') {
                    element.animate({
                        opacity: 0
                    }, 1000, done);
                } else {
                    done();
                }
            },
            removeClass: function(element, className, done) {
                if (className === 'ng-hide') {
                    element.css('opacity', 1);
                }
                done();
            }
        }
    });

    angular.element(animContainer).ready(function() {
        angular.bootstrap(animContainer, ['myApp']);
    });
</script>Code language: HTML, XML (xml)

Richer animation functionality can be achieved via CSS3 animations (instead of just using transitions.) The CSS (style) of the first animations example could be replaced with:

@keyframes anim-frames {
    0% { opacity: 1; background-color: #ffffff;}
    10% { opacity: 0.9; background-color: #ffffff;}
    20% { opacity: 0.8; background-color: #d0d0d0;}
    30% { opacity: 0.7; background-color: #d0d0d0;}
    40% { opacity: 0.6; background-color: #ffffff;}
    50% { opacity: 0.5; background-color: #ffffff;}
    60% { opacity: 0.4; background-color: #d0d0d0;}
    70% { opacity: 0.3; background-color: #d0d0d0;}
    80% { opacity: 0.2; background-color: #ffffff;}
    90% { opacity: 0.1; background-color: #d0d0d0;}
    100% {opacity: 0; background-color: #ffffff;}
}

.anim-hide.ng-hide-add.ng-hide-add-active {
    animation-name: anim-frames;
    animation-duration: 2s;
}Code language: CSS (css)

This would flash the container that is being hidden (alternating white and grey background.)