Chapter 2

Basic Principles and Concepts

Before we cover AngularJS in detail, we will start with an overview of the fundamental concepts of the AngularJS framework. This is particularly important because someone wishing to understand how AngularJS works needs to first understand a number of basic principles. After completing this chapter you will be able to understand the most important terms in the world of AngularJS and understand how the framework uses certain proven patterns and best practices to facilitate the development of client-side web applications.

Key Concepts

The AngularJS framework is based on some proven design patterns. In this section we present these patterns and explain their purpose in the context of AngularJS.

Model-View-Controller or Model-View-ViewModel?

The Model-View-Controller pattern

One of the most fundamental architectural patterns in software engineering is certainly the Model-View-Controller (MVC) pattern. Any developer who has written applications that include a graphical user interface must have used or at least heard about this pattern, which was first used in Smalltalk. In 1979 Trygve Reenskaug, a Norwegian computer scientist, formulated this pattern after recognizing that it is useful for GUI applications to separate responsibilities in terms of data storage, business logic and presentation and outsource them in separate layers.

Since the emergence of the first dynamic websites that dynamically assembled a response for each HTTP request, the MVC pattern has experienced a major boom. It was quickly recognized that it is useful to apply the MVC division of responsibilities to the realm of server-side web applications. Whether it is Ruby on Rails, Zend Framework or Spring MVC, every relevant web framework requires MVC as the basic architectural pattern. In this case, the pattern of its basic philosophy is quite simple.

As already mentioned, there is a strict division between data management (model), presentation (view) and business logic (controller). The original idea is that the user interacts with a view component (e.g. GUI) and user input is processed by a controller component. This controller component has the responsibility to check the entered data before processing it to make sure that the data conform with certain business rules, and to ensure that the model component manipulates the underlying data model accordingly. Furthermore, there is a communication mechanism between the model component and the view component that allows the view component to be notified of any changes in the model. As a result, the view component is ultimately responsible for ensuring that the user gets to see the current state of the data.

In the context of the classical round-trip based web application, the practical implementation of the MVC pattern usually means an application contains a collection of controllers, each processing requests on a certain URL. Once a client (usually a browser) sends an HTTP request to the server, the request is processed by the controller, which is responsible for the processing of the called URL. This controller now has the aforementioned responsibilities: data validation, data processing according to some business logic and reporting of possible manipulations to the model layer, which usually manifests itself in the form of a database and the appropriate access objects. Moreover, the controller has the task of merging the data and the HTML template, which uses the framework used to assemble an HTTP response. This HTTP response, in the form of dynamically generated HTML, is then sent to the client.

The generated HTML code that the browser ultimately receives for display is, in principle, part of the view component. The fact that the web in its original form is based on the round-trip principle and thus any form of HTTP communication requires that the client send a request and the server respond, means no smooth communication channel between the data storage layer and the HTML code displayed in the browser (view component) can be established. Thus, the view component of a classic web application is solely in the context of updating an HTTP response that must be preceded by an HTTP request. As such, interactivity suffers.

In summary, it is safe to say that in classic round-trip based web applications, the model, controller and a large part of the view logic reside on the server. This type of software is known as a thin client.

Although the round-trip-based processing is perfectly fine in classic web applications with little interactivity, to create modern applications you need a large part of the logic to be on the browser. The server in this kind of architecture is used only as a data source and memory by providing application data via a REST-based interface to the application running in the browser. Consequently, it is obvious, even in these so-called fat clients how the MVC pattern can be applied has to be considered.

The Model-View-ViewModel Pattern

The fact is the MVC pattern plays a predominant role in the browser. All popular JavaScript frameworks, including AngularJS, use this pattern as the basic approach to structure the application. However, often used in the area of client-side web application is an extended version of the MVC pattern: the Model-View-ViewModel pattern, or MVVM for short.

Originally, the MVVM pattern came from the Adobe Flex and Microsoft Silverlight environments. It has now established itself in the JavaScript world. Because the model layer, i.e. the application data, first of all is on the server and is externally served via a REST API, it makes sense to introduce a proxy layer on the client, which returns only the actual data that is currently used. This proxy layer is called a ViewModel.

Often the data needs to be transformed in a certain way before being fed to the display. Such data transformation is also the responsibility of the ViewModel. Finally, it also defines the functionality that we need within a display. So you could, for instance, define a function in a ViewModel that contains the logic for handling a button click. Figure 2.1 illustrates the relationship.

Figure 2.1: The Model-View-ViewModel pattern

In connection with MVVM, the concept of two-way data binding is also used frequently. You will learn this in detail later in the next section. In summary, the ViewModel layer has two primary tasks:

  1. Providing and transforming a section of the application data that is located on the server
  2. Providing the required functionality in the context of a display.

The actual implementation of the ViewModel concept is done using scopes in AngularJS. Scopes are covered in the next section.

Two-Way Data Binding and Scopes

Two-way data binding is a mechanism for automatically updating the data model when the user changes something in the GUI. The reverse route, in which automatic updates of views occurs in response to changes in the underlying data model, is also covered by the concept. The meaning of the name of this concept is data binding in two directions.

Because this concept is an integral part of AngularJS, we can save a lot of code we would otherwise have to write to implement the appropriate update logic itself. If you compare AngularJS with jQuery, you will learn that in jQuery you would have to register on DOM elements an event listener that will invoke a callback function when the event is triggered. The jQuery callback function would then modify the corresponding variable of the data model. Also, with jQuery the binding in the opposite direction must be taken care of manually. In other words, when your variable changes, you would have to implement some logic to update the value of the corresponding DOM element. It would involve a lot of so-called “boilerplate code,” dirty application logic that you can spare yourself in AngularJS thanks to two-way data binding.

In AngularJS the implementation of two-way data binding is essentially based on scopes. A scope consists of the set of all variables and functions that are defined in a particular context. The context is a specific part of the DOM (Document Object Model). It is important to understand that AngularJS can establish two-way data binding only for variables or functions that are defined in a scope. It is equally important to recognize that the framework creates a root scope for an application.

Another feature of scopes is the fact that they can inherit from each other through prototypes and thus form a hierarchy. Semantically, this corresponds to prototypal inheritance in JavaScript, which is a common way of offloading common data and logic in a higher-level construct to avoid redundancy.

If Scope B is inherited from Scope A, in plain English that means in the context of Scope B not only can you access the variables and functions defined in B, but also the variables and functions defined in A. However, note that the same named variables or functions can be overriden. Thus, if the same variable or function exists in both A and B, then the variable or function from Scope B hides the identically named variable or function in Scope A. In the context of Scope B then you can only guarantee a detour to access an identically named variable or function in A.

In addition to the scopes in two-way data binding, you need a mechanism to decide when to synchronize the user interface and the data model. You usually have the choice between dirty checking and an observer-based solution. AngularJS uses dirty checking.

As the name implies, dirty checking essentially consists of an algorithm that examines all relevant scopes for the current view to find polluted variables and, upon finding a discrepancy between the view and the ViewModel, executes some synchronization logic. Be warned that this algorithm is run frequently and, as such, must be highly optimized with regard to execution time, so as not to negatively affect the application responsiveness. Luckily, you do not have to worry about when this algorithm is executed as the framework takes cares of this. Nevertheless, you should keep in mind how this routine works so that you do not run into performance problems within your application. The most important rule that you should note is that you simply define data and functions in a scope when you actually need this in the view. All unnecessary data and functions in the view that are still defined in a scope increase the time taken to perform dirty checking without adding any value.

Inversion of Control and Dependency Injection

AngularJS also makes use of inversion of control (IoC) and automatic dependency injection (DI). Various platforms such as Java and .NET have for years offered a wide range of possibilities in order to use these patterns seamlessly in applications. Thanks to AngularJS, these concepts can now be found for the first time in client-side web applications.

The concepts address a fundamental problem in software development: How do you manage application components so that they are decoupled from the existing dependencies and so that you can achieve a high degree of flexibility and interchangeability? These are finally two important characteristics of high-quality software because they are responsible for ensuring that applications are testable and easy to adapt. Testable in the sense that you can easily write unit tests for your components.

JavaScript developers who have not used IoC and DI probably ask these questions: What is actually inverted? What are the advantages here? What is the difference between IoC and DI? And why are these patterns so essential for producing high quality software? For answering these questions we will talk briefly about IoC and DI.

To resolve an application component’s dependencies, in JavaScript you have four options:

  • Manual instantiation with the new operator
  • Integration via a global variable
  • Flexibility and interchangeability
  • Testability

If you think about it, the first three options are more detrimental than helpful when it comes to dealing with dependencies. The first option means that at design time you have to write code that instantiates a fixed component that is difficult, if possible at all, to change at runtime. The integration over a global variable comes with similar disadvantages and makes the application’s global namespace messy. The third solution through a service locator is—although better than the first two—not optimal, because you have to somehow access the service locator and request an instance of the actual dependency. By making use of the indirection provided by the service locator, dependency is decoupled from the component. However, you now have to obtain an instance of the service locator, which basically means you are simply moving the original problem. Therefore, the optimal solution would be to return the responsibility of object creation to the environment. This provides the basis of a convention that automatically determines what dependencies are needed by an application component and hand them to the component at runtime. As such, you do not need to rewire your application component when different dependencies are needed. You just leave this task to the environment.

Your component is no longer responsible for generating its dependencies, but this responsibility has been transferred to the environment. Thus, we speak of inversion of the control flow. In fact, it is this attribute that makes the IoC principle very important in software engineering and on so many levels.

To find an independent and unmistakeably comprehensible name for the IoC pattern in the context of dependency resolution of application components, the name dependency injection (DI) was chosen. DI is thus a special form of IoC.

Talking about the benefits of this pattern, you have to realize what opportunities arise in terms of flexibility and interchangeability, if you decide to use DI in your application. If you have no hard-coded dependencies anymore within an application component and have passed the responsibility of resolving dependencies to the environment, in a test you can tell the environment that it should give you a mock object instead of an actual object. Of course you can implement the mock object yourself. In a test it is a very simple matter to specify how certain dependencies should behave, allowing you to test a component in isolation.

In a test scenario, you obviously have a huge advantage as you can replace the actual implementations of certain dependencies transparently by using DI without re-compiling your application. Your software thus remains flexible and individual components remain interchangeable.

The test scenario presents a tremendous advantage, because you can replace the actual implementations of certain dependencies with little effort by using DI. Your software thus remains flexible and individual components remain interchangeable.

As you will see later in this book, DI is a central theme in AngularJS and provides you with an elegant handling of dependencies in most application components. Similarly, DI occurs naturally in tests. You can just replace dependencies for production with mock objects in a test.

Testability

Unlike most other JavaScript frameworks, AngularJS was designed from the start with a strong emphasis on testability. The basic design and the consistent utilization of dependency injection allow you to test all application building blocks in isolation as well as in the integration scenario extensively. The test-driven development approach allows you to implement client-side web applications. For all AngularJS application modules—especially for controllers, services, directives and filters—there is a concept for unit tests. In addition, you can use ngScenario to as a comprehensive domain specific language (DSL) to define end-to-end tests (E2E tests).

These are tests in which an individual application is regarded as a black box. Using DSL functions you can trigger user interaction and check the results if they meet expectations. The example in Listing 2.1 shows an example E2E test for fictitious user login.

Listing 2.1: E2E-Test for user login

describe('User Login', function() { 
  it('should successfully login the user', function() { 
    input('user').enter('John'), 
    input('password').enter('123asd'), 
    element(':button').click(); 
    expect(element('h1').text()).toEqual('Hello, Tom!'), 
  }); 
});

In addition, AngularJS brings its own mock service implementations that you can use to produce a given condition for a test. There is, for example, the $httpBackend mock object for the $http-Service, which prevents a genuine request to a REST-based backend and only checks whether the corresponding function of the $http-Service has been called. At the same token, you can easily emulate a REST-based backend and consider what would happen if a real REST service returns a particular record.

Summary of Key Concepts

  • AngularJS is based on the Model-View-ViewModel pattern (MVVM).
  • The ViewModel layer is implemented in the framework using scopes.
  • A very important feature in AngularJS is two-way data binding. It ensures that views are updated automatically when certain data is changed in the scopes. Also, the updating in the opposite direction is carried out automatically.
  • Two-way data binding is based on scopes. All data for this type of data binding must be defined in a scope.
  • Another key concept that is ubiquitous in AngularJS is dependency injection, i.e., the automatic resolution of dependencies.
  • AngularJS is designed from the ground up with testability in mind. Each design decision within the framework strongly motivates a developer to write clean and testable source code.

Application Building Blocks

AngularJS defines a set of application modules with clearly specified functions. Typically, an application consists largely of a composition of these building blocks. In this section we present these modules briefly to introduce you to the concept of AngularJS and explain how these components relate to each other.

Module

In AngularJS a module represents the largest structure that encapsulates a set of related application components. In particular, it serves as a container for controllers, services, directives and filters. Conforming with the definition of module in software engineering, an AngularJS module should have at least the following properties:

  • high internal cohesion
  • clearly defined API
  • high degree of reusability

Also at the module level AngularJS uses dependency injection. Therefore, other modules can be easily integrated as dependencies of a module. AngularJS automatically provides dependency resolution, as long as this module has been previously included with a <script> tag in the index.html.

An AngularJS application contains at least one module, which is automatically initiated and executed by the framework when the DOM is fully loaded. Using the ngApp directive you can tell AngularJS which of your modules is the start module. Many examples in the Internet are based on a single module, which contains all of the application components (controllers, services, directives, filters), which are necessary for the sample. This minimal type of composition is sufficient for most small sample applications. In a large project with multiple teams, however, you should think how you can divide parts into modules such that a high degree of reusability can be achieved. Contrary to what the official AngularJS Developer Guide (at http://docs.angularjs.org/guide/module) says that modules should be defined in terms of the contained technical components, our own experience dictates that a technical definition usually proves to be more useful in large projects. So, in a fictional application with a module called user management, in which the complete aspect of user management is implemented, there are almost always more modules than just the app-controllers module, the controller that contains all parts of the application.

In addition to your own application modules, there are a wide range of open source modules that are implemented and managed by third parties for AngularJS. These include modules such as angular-translate, Angular UI and AngularStrap.

The Controller

The controller contains the design requirements for scopes and thus defines the data and logic needed for a particular view. A controller instance gets its own scope in which variables or functions are defined. These variables and functions can then be accessed from within the view. As already mentioned, scopes can inherit from each other prototypally and thus form a hierarchy. A large part of this hierarchy is built on top of AngularJS when you nest controllers in your application.

In AngularJS controllers have a close connection to services. A service is obtained once again using dependency injection. This also means that you can replace a service with a mock implementation in a test.

When implementing a controller, you should adhere to certain guidelines. Controller implementations should be focused and their source code meaningful. This means that you should encapsulate any complex logic in a service that you then inject into your controller via dependency injection. Furthermore, controllers should not hold any data, but only provide references to data in their scopes. The actual data storage should in turn be encapsulated in a service that acts as a data access object. Any operation that engages in manipulating the DOM in a controller is a taboo. Whenever you find yourself needing to perform DOM manipulations, you should first consider whether you can instead change the default directives as ngRepeat, ngShow or ngClass. If you get into the situation of having to actually perform DOM manipulations, do this in a separate directive.

The Model

Unlike in many other JavaScript frameworks, in AngularJS data-holding objects, the so-called models, are ordinary JavaScript data types. This means that your models do not have to inherit from certain framework classes and no other conventions have to be followed. The definition of an ordinary object or array of primitive data types is sufficient to meet the requirements of AngularJS models. As mentioned before, AngularJS uses the concept of two-way data binding to synchronize models with their corresponding representation in a view. At this point we should mention again that this two-way data binding can only be applied to a model if the model is defined in a scope. In the example in Listing 2.2, you see how some valid model definitions, which include two-way data binding, can be established.

Listing 2.2: Model definition in a controller scope

angular.module('myApp') 
.controller('MyCtrl', function ($scope) { 
  // a simple JavaScript object 
  $scope.user = { 
    name: 'John Doe', 
    age: 27, 
    email: '[email protected]' 
  }; 

  // a simple JavaScript array 
  $scope.family = [ 
    'James Doe', 'Clarissa Doe', 'Ted Doe' 
  ]; 

  // a primitive value 
  $scope.loggedIn = true; 
});

Routes

A typical problem in single page applications (SPA) is the addressing of individual parts of the application via a URL in conjunction with the browser forward and backward functions. In this context, we also speak of deep linking. This aspect, which has a trivial solution for classic round-trip-based web applications, requires increased expense in client-side web applications. Fortunately, modern browsers offer a History API that allows you read and write access to the browsing history. This way, you can programmatically change the URL according to your application state, such as in the address bar of the browser, without a complete reload of the application.

Despite the simplicity of the History API, AngularJS goes a step further to abstract the logic in the form of routes. As such, you can conveniently map URLs on templates with their associated controllers.

Listing 2.3: Route definition for an AngularJS application

angular.module('myApp').config(function ($routeProvider) { 
   $routeProvider 
      .when('/', { 
         templateUrl: 'templates/mainTemplate.html', 
         controller: 'MainCtrl' 
      }) 
      .when('/user/:userId', { 
         templateUrl: 'templates/userDetailsTemplate.html', 
         controller: 'UserDetailsCtrl' 
      })
      .when('/user', {
         templateUrl: 'templates/userOverviewTemplate.html',
         controller: 'UserOverviewCtrl'
      }) 
      .otherwise({
         redirectTo: '/' 
      });
});

Listing 2.3 shows a route definition. Using $routeProvider you can define a mapping between a specific template URL and a controller by calling the when() function. The call to the otherwise() function defines a fallback mechanism that applies to all requests that do not match the predefined routes. As you can see in the example, a route may also contain a variable in its definition. Thus, the route /user can either be called without a path parameter or with the path parameter userId. If it is called with a parameter, you can access the parameter passed in the corresponding UserDetailsCtrl controller. We explain how this works in Chapter 3, “The BookMonkey Project.”

Therefore, routes make a AngularJS application a full single page application and enable application users to use deep linking. Users can easily share an application link that enables the application to render in a certain state.

Views, Templates and Expressions

In the context of AngularJS you often hear the terms views, templates and expressions. Now it is important to clarify the difference between these terms, especially because the demarcation of views and templates is slightly blurred and often leads to ambiguities.

Views and Templates

A template is an HTML fragment that contains a part of the HTML page, which the browser interprets and renders for display. Therefore, you you define the look of your user interface with templates. In AngularJS a template may contain expressions and directives.

A view is a part of the user interface. It is created at runtime when AngularJS compiles the necessary templates for the applicable part and replaces all expressions as well as directives with concrete values or DOM nodes. Formally speaking, a view is the result of an instantiated template. Or more accurately: A view is the combined result of instantiated templates, because most of your application views are based on multiple templates.

In everyday conversation, views and templates are used more or less interchangeably. However, it is sometimes necessary to understand the difference.

Listing 2.4 shows a simple template.

Listing 2.4: A simple AngularJS template

<div>
  <p>Hello, John Doe!</p>
</div>

As you can see, the template in Listing 2.4 is simply an HTML code. However, as you can see later, it can contain expressions and directives.

Expressions

Expressions are used in a template primarily to access data defined in a scope. So if you look at the MyCtrl controller again (see Listing 2.5), you can edit the new template to generate the user’s name from the scope (see Listing 2.6).

Listing 2.5: Our MyCtrl controller

angular.module('myApp') 
.controller('MyCtrl', function ($scope) { 
  $scope.user = { 
    name: 'John Doe', 
    age: 27, 
    email: '[email protected]' 
  }; 

  $scope.family = [ 
    'James Doe', 'Clarissa Doe', 'Ted Doe' 
  ]; 

  $scope.loggedIn = true; 
});

Listing 2.6: A template with a simple expression

<div>
  <p>Hello, {{user.name}}!</p> 
</div>

As you can see in the template in Listing 2.6, an expression is written in the curly braces. Within these parentheses you can use a subset of JavaScript to generate output. However, the expression cannot be easily evaluated using the JavaScript eval(). Therefore, there are special notes with regard to expressions:

  1. The evaluation is carried out not like eval() against the global window object, but against the scope that is valid in the context of the template.
  2. Expressions that evaluate to undefined or null do not generate output because potential exceptions such as ReferenceError or TypeError are caught and ignored.
  3. Control flow statements such as conditional statements or loops cannot be used.

You can also perform simple calculations in expressions (See Listing 2.7).

Listing 2.7: A simple computation in an expression

<div>
  <p>
    Hello, {{user.name}}!<br>
    Five years ago you were
    {{user.age - 5}} years old.
  </p>
</div>

We have mentioned that expressions exist primarily for evaluating scope data in templates. In conjunction with two-way data binding, this means AngularJS ensures that an expression is automatically evaluated again when the corresponding scope value has changed. Thus, the framework automatically updates the views if the underlying data has changed.

What makes expressions so powerful is the fact that you can use a formatting filter to reformat data. A more detailed explanation can be found in the subsequent subsection.

Filters

In AngularJS there are two types of filters. In the previous chapter we mentioned the formatting filter. On the other section of the same chapter, we discussed the collection filter, which can be used in conjunction with the ngRepeat directive to filter or transform a collection. The collection in this context is either an array or an object hash.

Formatting Filters

You use the formatting filter within expressions to format or transform the result of an expression. You can use the pipe syntax in an expression, as shown in Listing 2.8.

Listing 2.8: A filter applied to an expression

<div> 
  <p> 
    Hello, {{user.name | uppercase}}!<br> 
    Five years ago you were
    {{user.age - 5}} years old.
  </p>
</div>

In the example in Listing 2.8, the uppercase filter is applied to the evaluated expression user.name. Part of the filter set which ships with AngularJS, this filter transforms a string to uppercase. The resulting output of this template in the view would be the string in Listing 2.9.

Listing 2.9: The result of the uppercase filter

Hello, JOHN DOE!
Five years ago you were
22 years old.

In addition to the uppercase filter, AngularJS includes other useful formatting filters, which we will discuss later. In addition, you can define your own formatting filter. Listing 2.10 shows an example.

Listing 2.10: The alternatingCase filter

angular.module('myApp').filter('alternatingCase', function () { 
    return function (input) { 
        var output = '', 
            tmp; 

        for (var i = 0; i < input.length; i++) { 
            tmp = input.charAt(i); 
            if (i % 2 === 0) { 
                output += tmp.toUpperCase(); 
            } 
            else { 
                output += tmp.toLowerCase(); 
            }
        } 

        return output; 
    }; 
});

The alternatingCase filter may seem contrived, but its sole purpose of existence is to educate. All the filter does is alternate the case of the input string. The programming interface for the formatting filter is very simple. It receives the result of an evaluated expression as a function parameter and can then do anything to the input. Finally, it returns an output that is then displayed.

Listing 2.11: Using the alternatingCase filter

<div>
  <p>
    Hello, {{user.name | alternatingCase}}!<br>
    Five years ago you were
    {{user.age - 5}} years old.
  </p>
</div>

Listing 2.12: The result from the alternatingCase filter

Hello, JoHn dOe! 
Five years ago you were 
22 years old.

Collection Filters

In addition to formatting filters, which are used in expressions, there are also collection filters. In terms of the API, collection filters have the same structure as formatting filters, but they are used in a different context in conjunction with the ng-repeat directive.

At this point you do not yet know what directives are and what you can do with them. However, we should mention that the ng-repeat directive is used in a template to output the elements of an array or an object hash. You can think of ng-repeat as the declarative counterpart of the loop.

Listing 2.13: Using ng-repeat

<div> 
Family members of {{user.name}}: 
<ul> 
<li ng-repeat="member in family">{{member}}</li> 
</ul> 
</div>

In the example in Listing 2.13, the scope variable family references a string array that contains the names of family members. The ng-repeat directive makes sure that each element is rendered as an <li> tag in the view. As a result, you have a view that displays the family members of John Doe. The HTML code generated at run time by the ng-repeat directive is printed in Listing 2.14.

Listing 2.14: The HTML code result in the view

<div>
  Family members of John Doe: 
  <ul> 
    <li>James Doe</li> 
    <li>Clarissa Doe</li> 
    <li>Ted Doe</li> 
  </ul> 
</div>

The question that immediately springs to mind is: Can we filter this output so that only a subset of the collection is returned? This is exactly where the collection filters come into play. They ensure that the collection filtered based on certain elements or their elements are transformed in a certain way. In conjunction with ng-repeat we use the pipe notation again. The following example demonstrates the filter filter of AngularJS. Admittedly, this filter has not been very thoughtfully named, but it serves its purpose.

Listing 2.15: Using filters within ng-repeat

<div> 
    Family members of {{user.name}}: 
    <ul> 
        <li ng-repeat="member in family | filter:'clarissa'"> 
            {{member}}
        </li>
    </ul>
</div>

We use the filter filter here because we want to print only the names of family members that contain the word clarissa. The filter is not case-sensitive.

There are other collection filters in the standard repertoire of AngularJS, including limitTo and orderBy. As both affect the output of a collection, what they do should be clear from their names. Nonetheless, we present an example application that use them later in this book and explain them in more detail.

If none of these filters meets your needs, you can define a new collection filter. In the following example, we define a separate collection filter called endsWithDoe. With this filter, we want to ensure that only items ending with the string Doe are returned.

Listing 2.16: Our endsWithDoe filter within ng-repeat

angular.module('myApp').filter('endsWithDoe', function () { 
  return function (inputArray) { 
    var outputArray = []; 

    angular.forEach(inputArray, function(item) { 
      if (item.length >= 3 
      && item.substring(item.length - 3) === 'Doe') { 
        outputArray.push(item); 
      } 
    }); 

    return outputArray; 
  }; 
});

As you can see in the example in Listing 2.16, the API for collection filters is very similar to the API for formatting filters. The only difference is that in our filter function in this example, we get an array as input (rather than a string as in formatting filters), and AngularJS accordingly expects the filter function to return an array. Within a filter function you can manipulate the output array to achieve the desired behavior, or build a completely new array. In the endsWithDoe filter we choose the latter alternative. For the sake of completeness, the following example shows how to use our own collection filter. To explain the effects, we should also complement the original array to names that do not end with the string Doe.

Listing 2.17: Adding members to our array

angular.module('myApp').controller('MyCtrl', function ($scope) { 
  $scope.user = { 
    name: 'John Doe', 
    age: 27, 
    email: '[email protected]'
  };

  $scope.family = [ 
    'James Doe', 'Clarissa Doe', 'Ted Doe', 
    'Burk Smith', 'Samantha Jones', 'Bill Brooks' 
  ];

  $scope.loggedIn = true; 
});

Listing 2.18: Using our endsWithDow filter with ng-repeat

<div> 
    Family members of {{user.name}}: 
    <ul> 
        <li ng-repeat="member in family | 
         endsWithDoe">{{member}}</li> 
    </ul> 
</div>

If you run this example, you will find out that only three names, all ending with doe, are returned, even though the output array now contains six names.

Finally, we should do refactoring to make our filter more generic. It should not only be able to filter the string “Doe,” but any arbitrary string. Since the Filter API supports filter parameters, we can easily incorporate this change. In addition, we should also rename our filter endsWith.

Listing 2.19: The endsWith filter with parameters

angular.module('myApp').filter('endsWith', function () { 
    return function (inputArray, endsWith) { 
        var outputArray = [], 
            subString, 
            hasMinLen, 
            isSubStringMatching; 

        angular.forEach(inputArray, function(item) { 
            hasMinLen = item.length >= endsWith.length; 
            subString = item.substring(item.length 
                    - endsWith.length); 
            isSubStringMatching = subString === endsWith; 

            if (hasMinLen && isSubStringMatching) { 
                outputArray.push(item); 
            } 
        }); 
        return outputArray; 
    }; 
});

Essentially, AngularJS calls our filter function with a second parameter if we use filter parameters. We can now use it to adjust our logic according to the second parameter. The temporary variables subString, hasMinLen and isSubStringMatching are not necessary and were only introduced in order to avoid long lines in the function body.

For the sake of completeness, the new version of our templates is shown in Listing 2.20.

Listing 2.20: Using our endsWith filter

<div> 
    Family members of {{user.name}}: 
    <ul> 
        <li ng-repeat="member in family | 
         endsWith:’Doe’">{{member}}</li> 
    </ul> 
</div>

The difference here is the fact that we call the endsWith filter by passing a string parameter (“Doe”). The result in the view is the same as the example of the endsWithDoe filter. However, thanks to our refactoring, the endsWith filter has become reusable and can be potentially used in other parts of the application.

Services

Through its flexible AP, services can be used for a variety of tasks. As already mentioned, controllers should be made as thin as possible, which means that you usually outsource complex routines and algorithms to services. In fact, a service can be used not only by controllers, but pretty much by any application component. Some services can be used in other services. The integration of a service is done basically by using dependency injection.

Services have some special features that you need to consider when implementing an application. An essential property of services is the fact that each service is instantiated only once in an application. Thus, each service follows the Singleton pattern. The so-called injector in AngularJS ensures that each time you submit a service via dependency injection, the same instance is always reused. Thus, services are particularly suitable for logic and data reuse in more than one controller.

Another characteristic of services is the interchangeability and the definition of a clear and stable API associated with services. It is important that you design the public interface of your services so that you can easily replace an implementation. This is an important characteristic particularly with regard to the testability and the flexibility of your application, so you can, for example, define a service that will ensure that your application data is persisted. You could offer the typical CRUD operations in its public API. During the development of the overall system you could initially begin offering an implementation based on the HTML5 Local Storage API. When the backend is ready, you could replace the service implementation so that the service addresses a REST endpoint and stores the data on a server.

Besides the aspect of interchangeability and flexibility, the example just mentioned showcases another application of services. It makes sense to use services in an AngularJS application as data access objects because they can be fed to every controller that need the corresponding data.

Listing 2.21 shows the definition of a service.

Listing 2.21: The definition of a log service

ngular.module('myApp').factory('log', function() { 
  // Service Implementation 
  var log = function(level, message) { 
    console.log('[' + level + '] ' + message); 
  }; 

  // Public API 
  return { 
    error: function(message) { 
      log('ERROR', message); 
    }, 
    info: function(message) { 
      log('INFO', message); 
    }, 
    debug: function(message) { 
      log('DEBUG', message); 
    } 
  }; 
});

Listing 2.21 shows a simple service which defined a pretty rudimentary logging API and offers an implementation based on console.log(). The service implementation does not come close to a level that makes it useable in production, and is deliberately kept simple for educational purpose.

The interesting thing about this snippet is that we did not define the service using a function called service(). Instead, we used the factory() function. In fact, in AngularJS a service can be defined in one of several ways. Among them a service can be registered using the service() function. You usually use this method only for a specific application, namely if you want to register a property of an existing class as a service instance.

Specifically, there are five methods for defining a service. Table 2.1 contains an overview that briefly highlights these five methods.

The most common form is the so-called factory method, which we used in Listing 2.21. In addition to factory(), you can define a service using the value(), constant(), provider() and service() functions.

Service definition

API

Description

Provider

provider(...)

Basic API for defining a service that can be configured during the configuration phase. Must provide a $get() function that returns the appropriate service instance. Can be called using a constructor function or an object.

Factory

factory(...)

Built on top of the Provider API. Abstracted from the aspect of configurability to allow a more convenient service definition if configurability is irrelevant.

Constructor function

service(...)

Built on top of the Factory API. Instantiates the service instance using the new operator in conjunction with the given constructor. Thus, an instance of an existing class can be registered as a service.

Value as service

value(...)

Built on top of the Factory API. Allows the registration of a value as a service. The value can be a primitive type, an object or a function.

Constant as service

constant(...)

Registers a constant value as a service. The value cannot be changed at runtime.

Table 2.1: The five methods for service definition

Now, of course, you might ask this legitimate question: What is the difference between all these service definition methods?

As shown in Figure 2.2, the various service definition methods are mostly built on top of each other. The providerCache and instanceCache serve as a basic container for the provider or service instances of your AngularJS application. Using the provider() function you can access the providerCache and register a provider this way. Therefore, a service provider is a component that must define a $get() function, which is eventually called by the framework and must return a service instance. The singleton instances of your services are managed in the instanceCache. Before the actual service object is instantiated, however, you can configure the service object in the configuration phase of your application using a provider. You do not have this option with the other methods. Thus, the service definition for a provider is the most powerful but at the same time the most complex way to obtain a service instance.

Figure 2.2: The service ecosystem

A service factory, which we have used in the definition of our log service, internally uses the provider() function and simplifies the definition by abstracting from the aspect of configurability. This way, you can create a service definition by simply defining a factory function that returns a service object. The downside is that you can no longer configure your service.

Figure 2.2 also shows that the service() and value() functions are in turn built on top of a factory. Therefore, the service() function expects a constructor function next to the service name. Internally, the service() function creates a service factory, which eventually instantiates the service object using the passed constructor function. The fact that the instantiation is done internally using the new operator allows you to work within the constructor to define your service object. This means that you can register an instance of an existing class as a service instance quite simply.

The value() function is the simplest but also the most primitive way of obtaining a service definition. As the name implies, the value() function registers a value as a service. In this case, the value can contain a primitive data type such as a number or boolean. However, any objects or functions can thus be easily deployed as a service and be available throughout the application via dependency injection. From a purely technical point of view, the value() function creates a factory, which in turn encapsulates and returns the passed value in a service instance. Thus, internally the value() function uses the factory() function.

In addition to service definition methods that build on top of each other, there is also the constant() function. This function registers a constant value as a service. This means the registered value cannot change at run time. Therefore, the constant() function has direct access to both providerCache and instanceCache containers.

Now we will breathe a little life to the dry theory and clarify the description with some sample service definitions.

Based on our log service in Listing 2.21, which we have defined using a factory, we will now implement the same service using a provider in order to still be able to do some small configuration.

Listing 2.22: The log service definition with a provider

angular.module('myApp').provider('log', function() { 
  /* Private state/methods */ 
    var prefix = '', 
        suffix = ''; 

    // Service Implementation 
    var log = function(level, message) { 
        console.log(prefix + '[' + level + '] ' + message + suffix); 
    }; 

    /* Public state/methods */ 
    // Configuration methods / Public methods 
    this.setPrefix = function(newPrefix) { 
        prefix = newPrefix; 
    }; 

    this.setSuffix = function(newSuffix) { 
        suffix = newSuffix; 
    }; 

    this.$get = function() { 
        // Public API 
        return { 
            error: function(message) { 
                log('ERROR', message); 
            }, 
            info: function(message) { 
                log('INFO', message); 
            }, 
            debug: function(message) { 
                log('DEBUG', message); 
            } 
        }; 
    }; 
});

Listing 2.22 shows the definition of our log service with a provider. What is interesting in this code is the fact that we call the provider() function with the service name and a constructor function for the provider. As can be seen in Table 2.1, instead of a constructor function, we could alternatively pass an object that needs to be similarly constructed. In particular, this object would also have a function called $get(). This function will be called by AngularJS when the framework needs to create the appropriate service instance, as soon as it is requested for the first time as a dependency. In this case, this function is called exactly once for each service, because, as already mentioned, services are singleton objects.

Anyway, at this point we have chosen the alternative with the constructor function. In general, this option is also commonly used, because it allows you to define variables that you cannot access from outside. Specifically, in this example the prefix and suffix variables, which contain a prefix or suffix for our log messages. Also, the log variable that references the function, which is the basis of our API implementation, is “private” and not reachable from the outside.

On the other hand, the setPrefix() and setSuffix() provider functions, which are defined by using this, are public and during the configuration phase (in a config() block) are called to make the necessary settings for the service, namely define a prefix or suffix for all our log messages. The $get() function is also open to the public and thus potentially accessible from the outside. As already mentioned, this function has a special meaning within AngularJS and should therefore not be called from your own application code.

After preparing our provider to enable us to set a prefix or suffix, we should now turn to the actual configuration.

Listing 2.23: The configuration of our log provider in a config() block

angular.module('myApp').config(function (logProvider) { 
    logProvider.setPrefix('[myApp] '), 
    logProvider.setSuffix('.'), 
});

Listing 2.23 shows a possible configuration of our log service. To configure the service, we take the previously defined provider logProvider that is passed by the framework (via dependency injection). We use the setPrefix() and setSuffix() public instance methods to set the strings that will contain the prefix and suffix of every log message.

After our log service is configured, we present an example, which shows how we can use the service in a controller.

Listing 2.24: Using our log service

angular.module('myApp').controller('MyCtrl', function($scope, log) { 
  log.debug('MyCtrl has been invoked'), 
});

Listing 2.24 shows how easily we can use our log service in a controller, by simply specifying its name as a parameter to the controller function. We can then access the functions that belong to the public API. In the case of the log service, the functions are debug(), info() and error(), of which only debug() is used to produce output in the browser console.

Directives

Directives are probably the most interesting feature in AngularJS because the framework manifests its unique selling point through this feature. You can use directives to expand the HTML vocabulary. This means that you can invent new HTML tags and attributes. Using directives you can hide complex logic behind a tag or an attribute. Without doubt this concept is therefore one of the crucial aspects of AngularJS.

A major advantage is that for the first time you can create reusable web components using directives. For instance, you can write a directive to encapsulate certain UI widgets used frequently in your application and thus eliminate much of redundant code that would otherwise have to be written. By using a directive this way you can, for instance, create organization-wide component libraries with a consistent “look and feel.” You can also ensure that repetitive components do not have to be developed again.

Using directives, applications can also be described declaratively. You can design tags or attributes with names that are already known to a developer. Thus they have a semantic value. New developers can get a first idea of an application without having to look deep below the surface. Existing developers in your team will be pleased with the resulting clarity and related maintenance.

Directives are a powerful feature that is responsible for making sure that application code is structured and reusable. Unfortunately, the whole thing comes at a price, in the form of a very complex API.

Naming Convention

To illustrate the concept of directives in a realistic example, we now re-present our colorPicker directive from the previous chapter and discuss its details.

Listing 2.25: The definition of the colorPicker directive

angular.module('myApp').directive('colorPicker', function () { 

});

As you have learned in the prevoius chapter, you can define a directive using the directive() function. This function takes two parameters, the name of the directive and a factory function. Do not confuse the factory function here with the factory in the context of services.

By convention a directive name starts with a lowercase letter and follow the camel case notation if the name consists of several words. The illustration on the HTML tag or attribute defined by a directive is done through the mapping of camel case notation against the snake case notation. For example, spreadsheet, chart or colorPicker are valid directive names. The corresponding HTML tags would be <spreadsheet>, <chart> and <color-picker>. In the case of colorPicker, you can also use <color:picker> and <color_picker>. Since AngularJS uses the colon (:), hyphen (-) and underscore (_) as delimiters.

As already mentioned in the previous chapter, there are two other valid ways to call a directive, by using the data- or x- prefix. The reason for this is older browsers will only allow user-defined tags and attributes if they are prefixed by one of these prefixes. Therefore, you can also use <data-spreadsheet> or <x-spreadsheet> in your templates.

Definition with A Link Function

AngularJS expects the factory function, which you pass as the second parameter, to return either a function or a directive definition object. If you return a function, then this function is called the link function of the directive. In the case of a directive definition object, you can control in very fine granularity how the directive should behave through a lot of properties. Thus, returning a function (link function) represents a simple case and returning a directive definition object represents a complex case.

Listing 2.26: The definition of the colorPicker directive through the return of a link function

angular.module('myApp').directive('colorPicker', function () { 
  return function(scope, element, attrs) { 
    console.log("It's me, colorPicker!"); 
  } 
});

In Listing 2.26, we first consider the simple case where we return a function (a link function) in the factory function of our colorPicker directive. For each instance of our directive, the link function is called exactly once. Thus, within the link function you can register event handlers to perform DOM manipulations and perform other routines that are specific to that instance. In short, the link function allows you to perform an initialization for each instance of the directive. When calling a link function, AngularJS passes at least three parameters:

  1. scope. A reference to the valid scope for this directive.
  2. element. A reference to the DOM element affected by this directive.
  3. attrs. A reference to an object, by which you can access the HTML attributes of the DOM element.

The implementation of our link function in Listing 2.26 simply writes a message to the browser console. In order to view this minimal directive in action, we need to write a small template that uses the directive.

Listing 2.27: Using the colorPicker directive in a template

<div color-picker></div> 
<div color-picker></div> 
<div color-picker></div>

Listing 2.27 shows a sample template that uses our minimal colorPicker directive. As you can see in the template, we have created a new HTML attribute with our minimum directive definition. The attribute does not have to reside in a div tag and can be used in any other tag. So if you implement a directive by simply returning a link function within the factory function, then by default you create a new HTML attribute. To make the example feasible, we will also create a minimal application that contains three files.

  1. index.html. The entry point and base template of our application.
  2. app.js. The module definition of our application.
  3. color-picker.js. The definition of our colorPicker directive.

Listing 2.28: The index.html of our minimum colorPicker application

<!DOCTYPE html> 
<html ng-app="colorPickerApp"> 
<head> 
   <meta charset="utf-8"> 
</head> 

<body> 

<div color-picker></div> 
<div color-picker></div> 
<div color-picker></div> 

<script src="lib/angular/angular.js"></script> 
<script src="scripts/app.js"></script> 
<script src="scripts/directives/colorPickerDirective.js"></script> 

</body> 
</html>

Listing 2.28 shows the index.html of our minimal AngularJS application. It serves as the entry point for the browser and defines the basic template of our application. To prevent this example from getting unnecessarily complex, we have inserted the example template in Listing 2.27 in our base template.

Listing 2.29: The app.js of our minimalist colorPicker application

var colorPickerApp = angular.module('colorPickerApp', []);

Listing 2.29 shows the module definition of our colorPickerApp module, which AngularJS loads automatically when the browser has finished loading the DOM. The instruction for AngularJS to do so can be found in the ngApp directive in the index.html.

Listing 2.30: The color-picker.js file of our minimal colorPicker application

colorPickerApp.directive('colorPicker', function() { 
  return function(scope, element, attrs) { 
    console.log("It's me, colorPicker!"); 
  } 
});

Last but not least, we still need our minimal definition of our colorPicker directive (the color-picker.js file), which is shown in Listing 2.30.

Definition with A Directive Definition Object

As mentioned in the previous section, you can define a directive by returning a link function in the factory function. Let’s now take a closer look at the directive definition object (DDO). This type of definition is much more complex, but allows for a very fine-grained description of the behavior of a directive. You have learned such a definition in the previous chapter when we developed a colorPicker directive using such a DDO. Therefore, our discussion of this type of definition is also based on the colorPicker example.

Let’s get started. We will convert the directive that returns a link function from the previous section to a DDO.

Listing 2.31: The definition of the colorPicker directive using a directive definition object

colorPickerApp.directive('colorPicker', function() { 
  return { 
    link: function(scope, element, attrs) { 
      console.log("It's me, colorPicker as DDO!"); 
    } 
  };
});

Listing 2.31 shows the definition of our colorPicker directive using a DDO. In this form, this definition is equivalent to the definition in Listing 2.30. You can also define a link function in the DDO by assigning the function to the link property.

Next, we want to change our directive so that instead of defining an HTML attribute, it will define an HTML tag. We do this by using the restrict property of the scope in the DDO.

Listing 2.32: Defining the scope using restrict

colorPickerApp.directive('colorPicker', function() { 
  return { 
    restrict: 'E', 
    link: function(scope, element, attrs) { 
      console.log("It's me, colorPicker as DDO!"); 
    } 
  };
});

Listing 2.32 shows that we set the scope to E, which restricts the directive to HTML elements or HTML tags. Now we can only use the directive as an HTML tag. As such, we need to modify our template to that in Listing 2.33.

Listing 2.33: Using the colorPicker directive as an HTML tag

<color-picker></color-picker> 
<color-picker></color-picker> 
<color-picker></color-picker>

The value of the restrict property has to be a string that contains one or more letters from the set {'E', 'A', 'C', 'M'}. The letters have the following meaning:

  • E. HTML element or HTML tag
  • A. HTML attribute
  • C. CSS class
  • M. HTML comment

Therefore, directives can also be used in conjunction with a CSS class or an HTML comment.

It is essential to note that for most directives there is certain logic or a certain markup hiding behind the corresponding HTML tag or attribute. You usually implement the logic in the link function. As for the markup for a directive, you normally use the template or templateUrl property. For both properties, AngularJS expects a string value. You can use the template property to set the template markup directly as a string within the DDO. On the other hand, the templateUrl property expects a URL to the template.

In our colorPicker example, we work with a more complex template, so we store it in a separate file and set a reference to it in the templateUrl property.

Listing 2.34: Referencing the template of the colorPicker directive

colorPickerApp.directive('colorPicker', function() { 
  return { 
    templateUrl: 'templates/colorPickerTemplate.html',
    restrict: 'E',
    link: function(scope, element, attrs) { 
      console.log("It's me, colorPicker as DDO!"); 
    } 
  };
});

Listing 2.34 indicates that the template for our colorPicker directive can be found in the colorPickerTemplate.html file, which is located in the templates subdirectory. This is the same template we had in the previous chapter. For your reading convenience, however, we print it again in Listing 2.35.

Listing 2.35: The template definition of the colorPicker directive

R:<input type="range" 
        min="0" max="255" step="1" 
        ng-model="r"><br> 
G:<input type="range" 
        min="0" max="255" step="1" 
        ng-model="g"><br> 
B:<input type="range" 
        min="0" max="255" step="1" 
        ng-model="b"><br> 
A:<input type="range" 
        min="0" max="1" step="0.01" 
        ng-model="a"> 

<div style="width: 300px; height: 100px; 
        background-color: 
        rgba({{ r }}, {{ g }}, {{ b }}, {{ a }});"> 
</div>

Our colorPicker is now complete and the example will work. You can see in your browser that AngularJS creates three instances of the color picker. If you take a close look, however, you will find two major flaws.

So far, our color picker has no initial value. As a result, initially there is no color preview because the corresponding r, g, b and a scope variables are initially undefined and therefore the CSS background-color property cannot be rendered. Only after we have moved every slider at least once, will the variables be assigned appropriate values and a color preview appear.

On top of that, our color picker is not a self-contained component and has an unpleasant effect on the surrounding context. This is to say that the three instances are not independent. If we move, say, the r controller for one instance, the r controllers of the other two instances will move at the same time. The reason for this is that at this stage the instances do not have a scope yet. Thus, each color picker instance is currently still bound to the r, g, b and a variables from the root scope and modifies these values when a slider moves. This situation is visualized in Figure 2.3 again. The resulting two-way data binding leads to the interaction of colorPicker instances.

Figure 2.3: colorPicker instances access the root scope

Independent Scopes for Directives

Before we look at the problem with the initial values, we first need to make sure our colorPicker instances have their own scopes.

Listing 2.36: Defining an independent scope for the colorPicker directive

colorPickerApp.directive('colorPicker', function() { 
  return { 
    scope: true, 
    templateUrl: 'templates/colorPickerTemplate.html', 
    restrict: 'E',
    link: function(scope, element, attrs) { 
      console.log("It's me, colorPicker as DDO!"); 
    }
  };
});

Listing 2.36 shows that the directive instances receive their own scope by setting the scope property of our DDO to true. So, if you run the example, you will see that the color picker instances no longer interfere with each other. Each instance has its own scope, in which the r, g, b and a variables are defined. This means that two-way data binding works on the variables in each instance’s own scope, leaving the root scope untouched.

This solution makes us one step closer to a self-contained and reusable component. However, there is still a bitter aftertaste.

An instance of our colorPicker directive has its own scope, but that scope currently still inherits a prototype of its parent scope, which in this case is still the root scope. This means we can access the variable scope and function of the parent scope in our colorPicker directive and can potentially manipulate them. If we intend to create a self-contained and reusable component, then the current colorPicker directive is obviously not ideal. Sooner or later we may rely on certain features or variables from the parent scope and thus become dependent on the parent. As a result, our component can only function if it lives in this context. It is not a self-contained and reusable component.

In addition, inadvertent manipulation of a variable from the parent scope may lead to nasty and hard to identify errors, which will ultimately cause time-consuming debugging sessions.

To avoid this problem and provide a clean break from the parent scope, AngularJS introduces the concept of isolated scopes.

Isolated Scopes

You can create an isolated scope within a directive. Although an isolated scope is still part of the valid scope hierarchy of an application, such a scope does not inherit a prototype of its parent scope. This means that you have no way of accessing data or logic of the parent scope from within a directive’s isolated scope. Accordingly, you cannot inadvertently modify the parent scope. This means the isolated scope is a key concept when creating reusable and self-contained components.

Listing 2.37: Defining an isolated scope for the colorPicker directive

colorPickerApp.directive('colorPicker', function() { 
  return { 
    scope: {}, 
    templateUrl: 'templates/colorPickerTemplate.html',
    restrict: 'E',
    link: function(scope, element, attrs) { 
      console.log("It's me, colorPicker as DDO!"); 
    } 
  }; 
});

As shown in Listing 2.37, we tell AngularJS to create an isolated scope for our directive by assigning an empty object ({}) to the scope property of the DDO. Therefore, at this point we have a reusable color picker without dependencies on any context. We may still inadvertently manipulate a variable from the parent scope when implementing the directive logic in the link function. This constellation of the scope hierarchy can be seen in Figure 2.4.

Figure 2.4: colorPicker instances with isolated scopes

So far, so good. However, as already mentioned, with the isolated scope you can access data and functions of the parent scope. And if you think carefully about it, you will find that the majority of reusable components need to connect to their context. These connections can be useful for several purposes. One of them is for a component to receive data from its surrounding context.

Our colorPicker requires such access to its context. On the one hand, we want to pass initial values. On the other hand, we want to notify the environment when the colorPicker has changed a color value internally. So what now?

Despite the adjective isolated in isolated scope, AngularJS allows you controlled connections to the parent scope. There are three ways you can make such a connection. Probably the most often used connection is the one that establishes a two-way data binding to a variable in the parent scope. The framework also provides you the opportunity to create a unidirectional connection to the parent scope as well as to call a function defined in the parent scope within the directive. In the latter case, you can even call the directive function and pass internal data.

In our colorPicker directive we use the unidirectional link to set initial values and the function to notify the environment of the change of a color value. Listing 2.38 shows the whole thing.

Listing 2.38: The definition of an isolated scope with controlled connections to the parent scope

colorPickerApp.directive('colorPicker', function() { 
  return { 
    scope: { 
      r: '@initR',
      g: '@initG',
      b: '@initB',
      a: '@initA',
      onChange: '&' 
    }, 
    templateUrl: 'templates/colorPickerTemplate.html',
    restrict: 'E',
    link: function(scope, element, attrs) {
      console.log("It's me, colorPicker as DDO!"); 
    } 
  };
});

Listing 2.38 shows how you can establish a number of connections to the parent scope, despite using an isolated scope. As such, you can access the data and functions of the parent scope. The main factor is that you no longer assign an empty object to the scope property of the DDO. Instead, you specify a configuration object. The key in this configuration object is how you specify which scope variables from the directive’s isolated scope should connect to the parent scope. As already mentioned, there are basically three forms. These are specified by the prefixes =, @ and &.

  • =. Indicates that the scope variable in the isolated scope of the directive that is specified as the key is to establish a two-way data binding to a variable in the parent scope.
  • @. Indicates that the scope variable in the directive’s isolated scope that is specified as the key is to establish a one-way data binding to the parent scope.
  • &. Indicates that the scope variable specified as the key should reference a function in the parent scope and be able to invoke the function and pass internal data from the directive.

This will become clear when you look at how you use the colorPicker directive definition in Listing 2.38.

Listing 2.39: Using the colorPicker directive by specifying initial values

<color-picker init-r="255"
    init-g="0"
    init-b="0"
    init-a="1.0">
</color-picker>

Listing 2.39 shows a section of the template that uses our colorPicker directive. We also want to pass data to the directive despite it having an isolated scope. In this case, the data we are passing are the initial values for the individual color components. The HTML attributes used here, init-r, init-g, init-b and init-a, which correspond to our directive definition in Listing 2.38, must be specified in snake case notation. Because the scope variables for the color components are r, g, b and a (and not initR, initG, etc.), we need the HTML attributes in addition to the connection type (here: @) in camel case notation, hence initR, initG, initB and initA. The unidirectional binding works well here because we only want to set initial values, and thus do not need data binding in two directions. Figure 2.5 illustrates the relationship again.

Figure 2.5: Unidirectional binding for scope variables r, g, b and a

We now have an insulated scope assigned to our colorPicker directive to turn it into a self-contained and reusable component. In addition, we have defined five controlled connections to the parent scope. Specifically, these are the four unidirectional connections (initR, initG, etc.), with which we can pass initial values to a color picker instance, and a working link (&) that we will subsequently use to call a callback function when a color changes.

Before we discuss the implementation of the link function, we will first introduce the MainCtrl controller, in which we define the callback function. The key point is we can have this callback function in spite of having an isolated scope by using onChange.

Listing 2.40: The definition of the MainCtrl controller with the handleColorChange() callback function

colorPickerApp.controller('MainCtrl', function ($scope) { 
    $scope.handleColorChange = function(r,g,b,a) { 
        console.log('handleColorChange', r, g, b, a); 
    };
});

Listing 2.40 shows the implementation of the MainCtrl controller just mentioned. The handleColorChange() function, which we define in the controller scope, represents the callback function that we want to make accessible to the colorPicker directive. In this example, we respond to a color change in the color picker with a simple console output. In addition, we provide the value of each color component for review.

To maintain a good style, we define this controller in a separate file named mainCtrl.js. We also create a directory called controllers under the scripts directory for our controller. The mainCtrl.js file is then saved in the controllers directory. We have to modify our index.html file so that it will load the newly created mainCtrl.js file. In addition, we still have to use the ngController directive to determine for which DOM section of the controller a new scope should be defined. We have to make sure that the DOM element that initiates the section is a parent element of our colorPicker instance. Therefore, we add the ng-controller attribute to the <body> element. The result of these changes can be seen in Listing 2.41.

Listing 2.41: The index.html with the ngController directive

<!DOCTYPE html> 
<html ng-app="colorPickerApp"> 
<head> 
    <meta charset="utf-8"> 
</head> 

<body ng-controller="MainCtrl"> 

<color-picker init-r="255" init-g="0" init-b="0" init-a="1.0"> 
</color-picker> 

<script src="lib/angular/angular.js"></script> 
<script src="scripts/app.js"></script> 
<script src="scripts/directives/colorPickerDirective.js"></script> 
<script src="scripts/controllers/mainCtrl.js"></script> 

</body> 
</html>

Next, we change how we call the colorPicker directive so that we can pass a callback function. The relevant part of the index.html is shown in Listing 2.42.

Listing 2.42: Supplementing the colorPicker call by specifying the callback function

<color-picker init-r="255" init-g="0" init-b="0" init-a="1.0" 
        on-change="handleColorChange(r, g, b, a)"> 
</color-picker>

We use the on-change attribute to pass a reference to the handleColorChange() function to our colorPicker instance. Again, the snake case notation is used because we have written the onChange scope variable that will contain the reference in the directive definition. Recall that the mapping of names from the DDO to the corresponding HTML attributes is done by replacing the camel case notation with the snake case notation.

To check the connection function created, we should conduct a brief test and call the handleColorChange function via the onChange attribute in the link function of the colorPicker directive. Listing 2.43 prints the corresponding source code.

Listing 2.43: Call to the handleColorChange function via the onChange reference

colorPickerApp.directive('colorPicker', function() { 
  return { 
    scope: { 
      r: '@initR', 
      g: '@initG', 
      b: '@initB',
      a: '@initA',
      onChange: '&' 
    },
    templateUrl: 'templates/colorPickerTemplate.html',
    restrict: 'E',
    link: function(scope, element, attrs) { 
      console.log("It's me, colorPicker as DDO!"); 
      scope.onChange(); 
    }
  }; 
});

The scope hierarchy and the scenario just described are summarized in Figure 2.6. The rectangles with rounded corners symbolize scope variables that reference a function.

Figure 2.6: The connection function for the onChange scope variable

If you run the example, you will get the expected output in the console like the one shown in Figure 2.7.

Figure 2.7: The console output in Chrome

This means that despite the isolated scope in our colorPicker directive we can create a working link to the parent scope and to the scope of the MainCtrl controller, and thus can reach the handleColorChange function through the onChange scope variable. The output contains the color components but so far the value is undefined. However, it is also clear that is because we did not pass parameters in our call within the directive.

Well, now we can devote ourselves to the final requirement and implement the link function so that the handleColorChange callback function is only called on color changes. The final implementation of the link function is shown in Listing 2.44.

Listing 2.44: The final implementation of the link function of our colorPicker directive

colorPickerApp.directive('colorPicker', function() { 
return { 

  [...] 

  link: function(scope, element, attrs) {
    var COLORS = ['r', 'g', 'b', 'a'];

    COLORS.forEach(function(value) {
      scope.$watch(value, function(newValue, oldValue) {
        if (newValue !== oldValue) {
          if (angular.isFunction(scope.onChange)) {
            scope.onChange(generateColorChangeObject());
          }
        }
      });
    });

    var generateColorChangeObject = function() { 
      var obj = {}; 

      COLORS.forEach(function(value) { 
        obj[value] = scope[value]; 
      });

      return obj; 
    }; 
  }
};
});

Basically, the implementation is quite simple. First, we define an array that contains the names of our scope variables for the individual color values. We do this solely to keep the following source code clearer and shorter. Then, we iterate over this array (using forEach) and use scope.$watch to register a watcher for each color component. The $watch function expects two parameters. The first parameter is the name of the scope variable to be monitored for changes. To the second parameter we pass a function that will be invoked in the event of a change. The framework passes two parameters, newValue and oldValue, which contains the new and old values of the scope variable, respectively. We then check if the two values are equal and, if they are not, call the handleColorChange callback function, which can be reached through the onChange scope variable. Before that, we make sure that onChange actually references a function.

Now we introduce another AngularJS convention. The handleColorChange callback function is defined to take four parameters, namely the individual color components r, g, b and a. With the function, we can transfer these parameters to a parameter object. Figure 2.8 shows how such a parameter object would look like.

Figure 2.8: The parameter object when calling handleColorChange

Basically the key parameters of the object must match the parameter names in the template, so that the parameters are ultimately transferred correctly. In Figure 2.8 the necessary matching items are shown in bold. In our implementation, we use a small helper function called generateColorChangeObject to generate this parameter object.

At this point we are done. Using a directive we have created a self-contained and reusable component called colorPicker. It can be used in a template by specifying <color-picker>. Because AngularJS registered this directive under some other names, it can also be used using <color:picker> and <data-color-picker>. In addition, we may pass initial values to our color picker using HTML attributes. Finally, we have implemented a mechanism that informs the parent component by calling a callback function to notify a color has changed.

If you run the example and move any of the sliders, you will get a bunch of console output that should be similar to the one in Figure 2.9.

Figure 2.9: The final console output in Chrome

Besides the DDO properties mentioned here, there are other properties that you can use to control the operation of the directive. You can surely imagine that the potentials of directives are still far from being exhausted with the example presented here. However, in order to explain every single nuance of directives, you would need another book that explains this concept. Table 2.2 shows the overview.

DDO property

Valid values

Description

link

A function with a maximum of four parameters

Definition of the link function that for each directive instance is called exactly once. AngularJS passes the following 3 or 4 parameters to the function, depending on the require property:

- scope. The scope

- element. HTML element

- attrs. Access to the HTML attributes

- ctrl. The controller (if require is set)

compile

A function with a maximum of three parameters

Definition of the compile function that for each directive (not instance!) Is called exactly once. AngularJS passes the function three parameters:

- tElement. Template element

- tAttrs. Access to template attributes

- transclude. Link function for the transclusion

restrict

string

Restriction of the scope whose value can be Attributes (A),

Elements (E), CSS classes (C), comments (M) or a

combination of them

template

string

Definition of the HTML template

templateUrl

string

Definition of the URL to the HTML template, if the template is located in a separate file

scope

boolean or object

Scope definition

replace

boolean

Indicates whether or not the directive tag will be replaced by the directive template

require

string

Definition of a controller dependency of another directive

controller

function

More complex directives can have their own controller

transclude

boolean

Definition of a transclusion, if we are allowing an innerHTML field when calling the directive in the directive tag. Used in connection with the ngTransclude directive

terminal

boolean

Indicates whether the directive is to be processed in the template directive

priority

number

Determines the processing order when several directives act on the same DOM element. Directives with higher priority are processed before those with lower priority. If two directives have the same priority, the order of processing is not defined.

Table 2.2: The overview of DDO properties of directives

The Relationship to Polymer

Although the concept of directives and the associated creation of independent HTML elements make AngularJS unique, admittedly the concept still has weakness. Despite the level of isolation you can achieve with isolated scopes, instances of a directive are still part of the DOM. It means not only can you refer to the corresponding HTML element in the DOM, you can also formulate a CSS selector to select the DOM element defined by the HTML template of the directive. In other words, in directives you can only have isolation at the data level. At the DOM level, however, you can easily write a script that modifies the inner working of a directive, which can ultimately lead to nasty errors.

However, there is a reason for this weakness. In the initial phase of AngularJS, shadow DOM, custom elements, templates, etc, which would allow for isolation and encapsulation at the DOM level, were not even in the first draft of any W3C specifications. Thus, you had to live with this limitation and implement the concept of directives without this kind of isolation.

At the time of writing this book, we were one step closer. There were already W3C specifications even though they were not yet implemented in any browser. At least, there were first drafts, so that dedicated developers can use polyfills or shims. These are small software artifacts that compensate for functionality not available natively with the appropriate emulation of the same API. New browser functionality can thus be made available to a certain extent even in older browsers.

At this point, the Polymer project (http://polymer-project.org) comes into play. Polymer provides a polyfill for the aforementioned W3C specifications and supports the use of the functionality discussed, some even in older versions of Internet Explorer. Polymer goes a step further by defining genuine self-contained and reusable components on the basis of the specifications mentioned. Of course, it is also possible to use Polymer to develop your own components that have these properties.

Because of this, it is obvious that you should try to use the Polymer approach in the implementation of the directive. In fact, the developers of AngularJS have announced that the directive functions will be based on Polymer in a future version of AngularJS.

Summary

  • A module represents the coarsest structure in AngularJS. It serves as a container for a controller, filter, service and directive. In addition, it can have as many config() and run() blocks.
  • You use a controller to define an independent scope for a DOM. You should try to keep a service slim and outsource complex logic to a controller.
  • Unlike most other JavaScript frameworks, the model layer consists of ordinary JavaScript constructs such as objects or primitive data types. Nevertheless, the framework can offer the feature of two-way data binding using these ordinary JavaScript structures by employing dirty checking for changes.
  • Routes make an AngularJS application a single page application and provide us with deep linking.
  • Views are instances of templates and are generated at run-time, when the framework has processed the required templates. The template is the HTML code of our application.
  • There are two types of filters in AngularJS, the collection filter and the formatting filter.
  • The collection filter is used in conjunction of the ngRepeat directive to filter or transform the underlying collection.
  • The formatting filter is used in conjunction with expressions to format or transform an evaluated value before being output.
  • A service is a singleton that contains the bulk of an application’s logic. It can be used for a variety of tasks, including to act as data access objects.
  • Directives are at the heart of AngularJS and are therefore the unique feature of the framework. They allow us to define new HTML elements and attributes. These elements or attributes can create their own template and have their own logic.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.185.87