Chapter 7

Frequently Asked Questions

AngularJS Modules: How Do You Structure Applications with Modules?

What are modules? In software engineering, modules are building blocks that form a closed functional unit and exhibit a high degree of cohesion. According to this definition, a module should be responsible for exactly one task and modules should form an entire system through loose coupling. Generally, you should always try to create modular software. This is especially true when encapsulated functionality is being reused or when a project needs to be more structural due to its complexity. In addition, a module has a clearly defined interface, which is used by its users to integrate with the module. The internal implementation of the module can then be changed without making changes to the module user.

Modules in AngularJS

In AngularJS, a module consists of a collection of framework-specific components such as services, filters or directives (See Figure 7.1). These components define the interface of a module.

Figure 7.1: Possible module constellations in AngularJS

An AngularJS module always has a unique name that is specified by a string. In addition, a module can have one or more dependencies on other modules. The other modules must be loaded in such a way that they can be resolved. Listing 7.1 shows a small example.

Listing 7.1: The definition of a module with a dependency

// Init App Module
angular.module('yourAppName', ['yourAppDependency']);
// Define Dependency Module
angular.module('yourAppDependency', []);
angular.module('yourAppDependency').factory(...);
angular.module('yourAppDependency').filter(...);
angular.module('yourAppDependency').directive(...);

In the JavaScript code in Listing 7.1, a module named yourAppName is created. It requires the module yourAppDependency, which must also be loaded so that the dependency can be resolved. It does not matter which of the two modules is defined first, because AngularJS loads all defined and integrated modules asynchronously before running the application.

In the module yourAppDependency, the components that should be part of the module are registered using AngularJS functions such as factory(), filter() and so on. After the registration, the components in yourAppDependency are accessible to yourAppName. Since yourAppName is now the module that AngularJS should execute after the DOM is fully loaded, you have two options. First, you can use the ngApp directive to declare that the yourAppName module is the start module. Second, you can use the function angular.bootstrap() to execute a module programmatically. Other options are explained in the official documentation of Bootstrap at http://docs.angularjs.org/guide/bootstrap.

When Is Modularization Useful?

Defining a set of modules is not an easy task, because they cannot be generalized and must be resolved differently depending on the application. You need a lot of experience in order to make the right decision. This is often associated with an iterative process. There are no formal rules.

However, we can derive a few guidelines from our experiences with large projects.

The official AngularJS documentation at http://docs.angularjs.org/guide/module suggests that modules should be structured according to technical points of view. This means you should create a module for all directives. Another module for managing the services and yet another module for the filters.

In our experience, however, we found that division based on technical aspects means that code reuse is almost impossible in most cases. This is because components that solve different technical problems are often managed in the same module. This means you have to implement the entire module even if you only need a small part of a component in the module.

In our experience, an elegant module composition can be achieved by dividing modules according to technical criteria. All components that solve a technical problem are thus part of a module. This means that it is fairly likely that most of the contained components will actually be needed when reusing the module composition.

Since version 1.0, AngularJS structures its modules according to this method. Many technical components that used to be part of the core have now been outsourced to their own modules. The following list shows a part of the current module structure in AngularJS.

  • angular
  • angular-animation
  • angular-cookies
  • angular-route
  • angular-touch
  • angular-...

You can also see in the module division of AngularJS that the development in a main module evolves towards non-essential parts being outsourced into their own modules bit by bit. You can use this method very successfully in your own projects. When you develop projects in a family of products, it makes sense to outsource common concepts that are useful for all projects into utility modules or common modules. You can thus significantly reduce development time and delay. However, this usually requires more synchronization effort.

For utility modules, you should remember that these concepts are not only helpful within the context of AngularJS. A good method should allow the development of some desired functionality independent from framework conventions and provide an adapter module for the corresponding framework (in this case, AngularJS). Thus, other teams that are working with different JavaScript frameworks can also benefit from the module.

If you are developing an open source module, you can use Bower to make it accessible to the public. At this point, you need a correct version control strategy. In the open source world, the semantic version control from Tom Preston-Werner has established itself as an unofficial standard. Its website is http://semver.org.

Due to the fact that a module in AngularJS is known by its name and there is no authority to determine who is allowed to use what prefix, name collisions cannot be avoided. If two modules have the same name, the most recently loaded module overwrites the previously registered module without warning. As such, you and your colleagues should agree on a naming convention for the whole organization. A good source for existing modules is the module repository at http://ngmodules.org. Listing 7.2 shows a possible pattern for naming your AngularJS modules.

Listing 7.2: Naming conventions for modules

// <namespace>.<name>-<optional-type>
angularjsde.bookmonkey
angularjsde.bookmonkey-i18n
angularjsde.bookmonkey-util
angularjsde.bookmonkey-auth

Directory Structure

In the world of JavaScript, there is no convention for directory structure. However, you can learn best practices from successful projects. The structure shown in Listing 7.3 is such an example.

Listing 7.3: Directory structure for a module

module-name/
    scripts/
    controllers/
    directives/
    filters/
    services/
    ...
    module.js
  styles/
  views/
  test /
    controllers/
    directives/
    filters/
    services/
    ...

This structure follows the recommendation of the official AngularJS documentation. However, there are advantages with regard to process automation, because you can use the wildcard patterns. In the file module-name/scripts/module.js, the module is initialized along with its dependencies.

Listing 7.4: A module.js file

angular.module('module-name', ['module-dependency']);

The following approach is highly recommended for the organization of various modules in an application.

Listing 7.5: Directory structure for various modules in an application

bower_components/
    bower-component-a/
    bower-component-b/
    ...
projectname_components/
    project-module-a/
   scripts/
    controllers/
    directives/
    ...
    module.js
    project-module-b/
common_components/
    common-module-a/
   scripts/
    controllers/
    directives/
    module.js
    ...
    common-module-b/
scripts/
  config/
    routeConfig.js
  module.js

In this structure, all components that are managed by Bower are located in the bower_componenets directory. Modules specific to your project can be managed in a directory named projectname_components, where projectname is to be replaced with the actual name of the project. Modules that are required by other applications and teams are located in the common_components directory.

Summary

  • In this section we explained the general definition of a module and examined modules within the context of AngularJS.
  • In addition, we introduced a directory structure for modules and projects.
  • There are already numerous AngularJS modules listed in various ways. These modules usually can be downloaded from Bower.

Promises: How Do You Deal with Asynchronicity?

Asynchronicity and Non-Blocking Callbacks

A JavaScript application is executed in the browser in a single thread. This thread is often called the event loop. You have to make sure not to block an application’s event loop for a long time. Otherwise, there would be a delay for the entire application.

There are, however, callbacks that require a certain amount of processing time before they can deliver valuable results for the application. These include HTTP requests. An HTTP request will block the entire application while the application is waiting for a response from the server. These blockages are not common, despite the fact that there is only one thread for the running application. How can an application respond quickly although there is only one thread for execution? The answer is asynchronicity and non-blocking command functions.

Asynchronicity and non-blocking command functions are ubiquitous in JavaScript. Listing 7.6 shows an example of an asynchronous function callback. The code sends a HTTP request to a server and prints the data from the HTTP response in the console. To achieve this, it calls the makeHTTPRequest() function with two parameters. The first parameter, url, specifies the target address for the HTTP request. The second parameter is a callback function that will be executed as soon as an HTTP response is received.

Listing 7.6: Calling an asynchronous function with a callback function

var url = 'http://mydomain.com/api/user/42';

var user;

makeHttpRequest(url, function(response) {
    user = response.data.user;
    console.log('User data', user); // user is defined!
}); // makeHttpRequest() is async / non-blocking!

console.log('User data', user); // user is undefined!

In the example, the variable user is used twice by the console.log() function. The first time is immediately after the calling of makeHTTPRequest() and the second time is in the anonymous callback function. The log output that immediately follows the makeHTTPRequest() call will be printed first. However, the variable user is still undefined at this point, because no HTTP response has been received.

Since makeHTTPRequest() is an asynchronous non-blocking function, calling it does not block the execution of your application, although the HTTP request is still being processed. As soon as an HTTP response is received, the callback function that was passed as the second argument to makeHTTPRequest will get executed. In this callback function, the variable user is assigned the value of response.data.user and its value is printed in the console. In this case, however, the variable user is defined, as long as there was no error in the processing the HTTP request and the corresponding User object is defined in response.data.

What Is A Promise?

Things become complicated if you want to execute multiple asynchronous callbacks that are dependent on each other. An asynchronous function callback is dependent on another asynchronous function callback if the former requires data for the callback that is first obtained from another callback. As you can see in Listing 7.7, the pyramid of doom arises in common source code indentation, which repeatedly pushes the script further to the right. Now imagine if you had to intercept and trace the errors in addition to the normal processing. It is obvious that a few asynchronous callbacks can lead to unreadable code.

Listing 7.7: A pyramid of doom

makeHttpRequest(url, function(response) {
  performOperation(response.data.user, function(result) {
    calc(result, function(calcResult) {
      doSomething(calcResult, function() {
        ...
      });
    });
  });
});

How do you deal with asynchronous callbacks elegantly? In the world of JavaScript, promises have been established as a way to tackle the problem. In other programming languages, promises are often called futures. There is an open specification of this concept called promises/A+ (http://promises-aplus.github.io/promises-spec). This specification deals with the interoperability of various promise implementations. Listing 7.8 shows the same problem that was previously solved with the callback function. It now returns promises with the help of asynchronous operations. A promise represents an asynchronous operation where a result occurs and through it you can deal with asynchronous APIs elegantly.

As you can see in Listing 7.8, the return value or error is automatically passed to the next asynchronous callback (downstream value and error propagation). This means with promises you can write asynchronous callbacks in a much more readable way. Additionally, you can deal with errors more elegantly.

Listing 7.8: Asynchronous operations that return promises

makeHttpRequestPromise(url)
.then(function(response) {
  return response.data.user;
})
.then(performOperation)
.then(calc)
.then(doSomething, function(error) {
  console.log('An error occurred!', error);
});

In this example, makeHTTPRequestPromise() returns a promise object. The most important function of a promise is the then() function. You can use then() to define which function should be called back when the result of the asynchronous operation is available or when an error occurs. To achieve this, the then() function takes two parameters. The first parameter is a success function, which will be executed when the asynchronous callback is successfully executed and the result is available. The second parameter is an error function, which is executed when an error occurs during a callback. In the following listing, you can see a summary of an API that uses a promise.

// onSuccess and onError are functions!
promise.then(onSuccess, onError)

Because the return value is automatically passed to the next asynchronous operation and every operation returns a promise, you can chain then() callbacks as shown in Listing 7.8. However, you have to build in a transformation step between the callback of makeHttpRequestPromise() and performOperation() in order to provide the performOperation() function with the data from the response.data.user object (instead of the entire response object). You deal with any possible error in the last callback by giving the then() function an anonymous error function that merely prints a message in the console, in addition to the success function doSomething. Since potential errors are automatically provided, it is sufficient that you deal with the errors at just one location.

To show you how you can implement your own asynchronous operations that are based on promises, we will use Q. Q (http://documentup.com/kriskowal/q/) is a popular implementation of the promises/A+ specification. In addition, we decided to use Q because the AngularJS implementation of promises is also based on a subset of Q.

In Listing 7.9 we outline a possible implementation of the makeHTTPRequestPromise() function with Q. This function is a wrapper function that encloses the callback-based function makeHTTPRequest() in such a way that a callback contains a promise as response. To achieve this, we use the function Q.defer(), which returns a deferred object. This deferred object is the essential component needed to transform a callback-based call into a promise-based call. We store the reference of the object in the local variable deferred.

Listing 7.9: Transforming a callback-based call to a promise-based call

// Wrap makeHttpRequest
var makeHttpRequestPromise = function(url) {
  var deferred = Q.defer();
  makeHttpRequest(url, deferred.resolve, deferred.reject);
  return deferred.promise;
};

The deferred object offers access to the accompanying promise via the promise property. You pass this promise to the makeHTTPRequestPromise() function. Thus, you fulfill the requirement that makeHTTPRequestPromise() return a promise. Callers can now use then() to register a success or error function, as shown in Listing 7.8. You now have to make sure that the promise is fulfilled or rejected, depending on the makeHTTPRequest() callback. You can achieve this with the functions resolve() and reject(). With resolve() you can treat a positive fulfillment, while the reject() function makes sure that the promise is rejected in the case of an error.

You have just seen that the makeHTTPRequest() function accepts a callback function as a second parameter that it will call back in the event of a success. Usually, callback-based asynchronous functions also offer an error handler. This is solved by using the respective callback function. It is thus assumed that the makeHTTPRequest() function expects an error function as a third parameter, as shown in Listing 7.9. This means that you can provide the makeHTTPRequest() with the respective function reference deferred.resolve and deferred.reject as the second and third parameters, respectively, in order to fulfill the promise or reject the error. This way, we have transformed our callback-based function makeHTTPRequest() to a promise-based function named makeHTTPRequestPromise().Q also offers tools for formatting source code more elegantly with promises. These are, however, not relevant in the context of AngularJS.

Promises in AngularJS

You can use promises in AngularJS. Practically all operations of the provided components return a promise. This includes the $http service, which simplifies the formulation of HTTP requests. The implementation of promises in AngularJS is based for the most part on a subset of Q. You can access this implementation by providing a $q service through dependency injection.

Compared to Q, there are a couple of differences in the internal implementation of AngularJS which is due to the fact that the framework must integrate the implementation in the context of dirty checking seamlessly. As was discussed in Chapter 4, “Extending the Application,” scope manipulations that occur outside AngularJS require a manual callback of dirty checking via $scope.$apply(). To ensure that this does not force the browser to repaint the DOM elements after an asynchronous operation, these functions are executed with the help of a help function, which deals with this problem in $rootScope.

Listing 7.10 shows the makeHTTPRequest() example in an AngularJS service called promisesService. For this, we need our application’s $rootScope in addition to the $q service, because we have to manually activate the dirty checking after the execution of the asynchronous function. If we did not do this, our AngularJS application would not be notified of changes triggered by this function.

Listing 7.10: Using $q in AngularJS

angular.module('app').factory('promisesService',
function ($rootScope, $q) {
  return {
    makeHttpRequestPromise: function (url) {
      var deferred = $q.defer();

      // manually trigger dirty checking
      $rootScope.$apply(function () {
        makeHttpRequest(
          url,
          deferred.resolve,
          deferred.reject
        );
      });

      return deferred.promise;
    }
  };
});

angular.module('app').controller('PromisesCtrl',
function ($scope, promisesService) {
  promisesService
  .makeHttpRequestPromise('http://server/user')
  .then(function(result) {
    $scope.user = result;
  });
});

By embedding promisesService at this point (See PromisesCtrl), we can use the then() function after the call to promisesService.makeHTTPRequestPromise() to handle successes or errors.

The $q implementation by AngularJS offers additional API functions to make working with promises easier. For example, the $q.all() function expects an array of promises and returns a promise. The returned promise is resolved after all promises in the array are resolved. $q thus allows you to execute a row of asynchronous operations synchronously, as long as they do not require one another. In addition, you can execute a resulting operation which then processes the individual results from the asynchronous operations. As usual you can specify the resulting operation in the form of a success function with then() (See Listing 7.11). This success function is thus executed when every individual asynchronous operation is completed and their result is available. In this case, AngularJS returns the individual results in an array. If at least one of the asynchronous operations causes an error and its promise is rejected, the resulting promise is also rejected.

Listing 7.11 presents the implementation of the PromiseCtrl controller. This time, we ask AngularJS to inject the $q service, which provides the function $q.all. We pass two promises to $q.all(), both returned by promisesService.makeHTTPRequest(). This is how we test two different user resources. We assign the returned array containing the individual results to the scope variable users.

Listing 7.11: The implementation of PromiseCtrl

angular.module('app').controller('PromisesCtrl',
function ($scope, $q, promisesService) {
  $q.all([
    promisesService
      .makeHttpRequest('http://server/user/1');

    promisesService
      .makeHttpRequest('http://server/user/2')
  ]).then(function(resultsArray) {
    $scope.users = resultArray;
  });
});

$q.when() is another useful function in the $q service. This function allows you to simulate a promise-based API. You can pass a value to this function and it will return a promise that represents this value. In addition, with this function you can transform a promise belonging to a third party library into a $q promise. This only works when the foreign promise implements the promises/A+ specification.

To simulate an asynchronous API with $q.when(), let us use the BookDataService from the BookMonkey project one more time (See Chapter 3, “The BookMonkey Project.”).

Before introducing the $http service in Chapter 4, “Extending the Application,” we did not return a promise from a function. Instead, we returned the data directly. We did so because no asynchronous operations had been executed up to that point and we did not wish to make the example unnecessarily complicated. Our solution was problematic, however, because with the introduction of the $http service, we had to change every part of our application that had called our service. The API for BookDataService was finally based on promises after this change. Therefore, we had to deal with the corresponding then() callbacks. It would have taken less effort if we had simulated the soon-to-be asynchronous API with $q.when() before introducing the $http service.

Listing 7.12 shows the steps that would have been necessary to simulate a promise-based API. A rewrite of the getBookByIsbn() function is shown. We simply need to wrap the return value with the $q.when() function instead of returning it directly. The return value of getBookByIsbn() is now a promise representing the actual return value. We can now define our success and error functions with then().

Listing 7.12: Simulating a promise with $q.when()

// BookDataService
bmApp.factory('BookDataService', function($q) {
  [...]
  // Public API
  return {
    getBookByIsbn: function(isbn) {
      var result = srv.getBookByIsbn(isbn);

      // use $q.when() to simulate Promise based API
      return $q.when(result);
    },
    [...]
  };
});

// BookDetailsCtrl
bmApp.controller('BookDetailsCtrl',
function ($scope, $location, $routeParams, BookDataService) {
  var isbn = $routeParams.isbn;

  BookDataService.getBookByIsbn(isbn).then(function(res) {
    $scope.book = res.data;
  }, function(error) {
    console.log('An error occurred!', error);
  });
});

Testing Promises in AngularJS

Testing asynchronous functions is no easy task. Fortunately, you can use the Jasmine framework. Jasmine offers special functions such as runs() and waitsFor(). You use runs() to execute an asynchronous function. You use waitsFor() to wait for a certain condition to be met within a given timeout. The details on the testing of asynchronous functions can be found in the official Jasmine documentation at http://pivotal.github.io/jasmine/#section-Asynchronous_Support.

AngularJS has another extension for asynchronous APIs that are based on promises. This significantly simplifies test formulation. By embedding the test module ngMock, you can perform the asynchronous execution of promises synchronously and thus decide when a promise should be resolved with a specific function callback.

Listing 7.13 shows the BookDataService that we artificially extended around the function promiseFn(). With the $q.when() function, this function returns a promise representing “BookMonkey”.

Listing 7.13: Artificial extension of BookDataService for testing promises

bmApp.factory('BookDataService', function($q) {

  [...]

  // Public API
  return {

    [...]

    promiseFn: function () {
      return $q.when('BookMonkey');
    }
  };
});

To test this function, we create a new test as shown in Listing 7.14. In this test, we call the function BookDataService.promiseFn() and receive a promise as a return value. By calling expect(), we can test if the returned object is a promise. Subsequently, we define a variable result and pass it to the success function to the call to then() function. By passing result to the expect() function, we can inquire if result has the value undefined and thus has not yet received a value.

Next, we can invoke the promise synchronously by calling $rootScope.$apply(). Recall that using this function we can activate dirty checking manually.

After invoking the promise manually, we can test if we have a definite result and if this result corresponds to the string “BookMonkey”. We do this by using two further expect() calls.

Listing 7.14: Testing a promise-based function with the synchronous invocation of the promise

it('should include a promiseFn() function',
  inject(function ($rootScope, BookDataService) {
    var promise = BookDataService.promiseFn();
    expect(promise).toBeDefined();
    expect(promise.then).toBeDefined();

    var result;
    promise.then(function (data) {
      result = data;
    });

    expect(result).toBeUndefined();

    // Resolve promise (execute success function)
    $rootScope.$apply();

    expect(result).toBeDefined();
    expect(result).toBe("BookMonkey");
  })
);

Summary

  • In this section, we discussed how you can deal with asynchronous functions elegantly in JavaScript. Many asynchronous APIs are based on callback functions. Since the nesting of asynchronous callbacks can result in unreadable constructs (especially the pyramid of doom), we introduced promises to deal with the problem.
  • There is an open specification for promises called promises/A+.
  • This specification makes sure that different promise implementations are compatible with each other.
  • We looked at the promise implementation Q.
  • We can implement a subset of Q in AngularJS
  • In AngularJS, we can access this subset via the $q service.
  • At the end of this section, we looked at how to implement asynchronous operations based on promises with the $q service.
  • We have also shown how we can simplify the testing of promise-based APIs in AngularJS. The framework allows us to synchronously invoke promises in a test.

AngularJS and RequireJS: Is This the Right Mix?

What Is RequireJS?RequireJS, which can be downloaded form http://requirejs.org/, is a JavaScript framework that enables the asynchronous loading of files and modules. It allows you to define modules and their dependencies. In doing so, RequireJS implements the asynchronous module definition (AMD) API, which is an unofficial standard for the definition of modules in the JavaScript world. The AMD API offers the following advantages:

  • Dependencies no longer have to be manually sorted. Instead, they are resolved implicitly.
  • Dependencies can be asynchronously loaded as soon as they are needed.
  • AMD API deters the use of global variables.
  • Module names can be saved over in order to be exchanged in a test by using a mock implementation.
  • The framework automatically recognizes which dependencies are currently required and loads these in the correct order. RequireJS adds a script tag in the head tag of our application for every dependency. As soon as the tag is added to DOM, the browser attempts to load this file and execute it. This can speed up the initial loading.

A RequireJS Example

We demonstrate how to use RequireJS in a small project. The project’s structure in shown in Listing 7.15.

Listing 7.15: The directory structure for the RequireJS example

index.html
scripts/
  app.js
  require.js
  helper/
    util.js
    i18n.js

The project contains an index.html file, in which we load our RequireJS script (see Listing 7.16). Note the extra attribute data-main, which defines the starting point of our application.

Listing 7.16: The index.html for our RequireJS example

<!DOCTYPE html>
<html>
<head>
  <title>My Sample Project</title>
  <!-- data-main attribute tells require.js to load
    scripts/app.js after require.js has loaded. -->
  <script data-main="scripts/app" src="scripts/require.js">
  </script>
</head>
<body>
  <h1>My Sample Project</h1>
</body>
</html>

After the browser loads the script, it starts executing RequireJS. The script defined with the data-main attributes (script/app) is now reloaded and executed. In app.js, we use the AMD API and introduce the execution of the provided function with require() (See Listing 7.17). The function, however, is first executed when all the dependencies have been completely loaded. The dependencies are provided as an array of strings via the first parameter. In our case, we require the module helper/util as a dependency. This module provides the function to be executed as a first parameter.

Listing 7.17: The starting point (app.js) of our RequireJS application

require(["helper/util"], function(util) {
  var output = util.translateFn("Hello World");
  console.log(output);
});

You define a module with the RequireJS define() function as shown in Listing 7.18. You can specify dependencies for a module in an array. In our util module, we specify the module helper/il8n as a dependency. In the util module we use the translate() function from the il8n module. In this example, the util module returns an API object that provides the public function translateFn(). As such, we can make the local function translate() available to other modules that want to use it.

Listing 7.18: The util module defined with RequireJS

define(["helper/i18n", function (i18n) {
  var translate = function(input){
    return "Translated: "+i18n.translate(input);
   };
  
  // Public API
  return {
    translateFn: translate
  };
});

Figure 7.2 shows the dependencies of our example project graphically.

Figure 7.2: Dependencies Inside of our Example Project

You can therefore have a clean division of components in a simple JavaScript application by defining an AMD module for each component.

In addition, you do not have to deal with the correct loading order of various JavaScript files in your index.html, because RequireJS takes care of this.

AngularJS and RequireJS

In this section you will learn how to implement AngularJS and RequireJS together. To this end, you have to extend an AngularJS project to accept certain RequireJS-specific constructs. We have decided to implement these extensions based on our BookMonkey project.

The BookMonkey version from the last section of Chapter 3 acts as a basis for the introduction to RequireJS. Recall that in that section, we introduced navigation using routes. The idea now is to define the various AngularJS components in their own AMD modules. In addition, we will create a requireConfig.js file, which acts as the entry point to our application. We will also manage the dependencies with Bower. The directory structure is shown in Listing 7.19.

Listing 7.19: The directory structure for BookMonkey with dependency management with Bower

bower_components/
  angular/
    angular.min.js
  angular-route/
    angular-route.min.js
  requirejs/
    require.js
scripts/
  config/
    routes.js
  controllers/
    book_details.js
    book_list.js
  services/
    book_data.js
  app.js
  requireConfig.js
index.html
bower.json

As the first step, we include RequireJS in our index.html. We employ the data-main attribute to demarcate the entry point. As shown in Listing 7.20, this attribute references our AMD module scripts/requireConfig, which is defined in the file scripts/requireConfig.js. In addition, we remove or comment out the previous script tags, because RequireJS takes care of file loading.

Listing 7.20: The index.html of BookMonkey with RequireJS

<script src="bower_components/requirejs/require.js"
  data-main="scripts/requireConfig"></script>

<!--
  These script tags are now obsolete!
  <script src="lib/angular/angular.min.js"></script>
  <script src="lib/angular-route/angular-route.min.js"></script> 
  <script src="scripts/app.js"></script>
  <script src="scripts/controllers/book_details.js"></script>
  <script src="scripts/controllers/book_list.js"></script>
  <script src="scripts/services/book_data.js"></script>js
-->

Next, we configure the AMD module angular and angularRoute, which are required by our application (See Listing 7.21). To this end, we can use the require.config() function from RequireJS. This function receives a configuration object. In order to embed AngularJS, we require the attribute paths. This attribute allows us to enter dependencies that cannot be found directly under the baseUrl path. In addition, we give these dependencies a solid module name. Since AngularJS and the ngRoute module are external dependencies that we can only manage with Bower, we specify here the relative path to our bower_components directory.

To make sure that our AngularJS is correctly embedded, we have to tell RequireJS that it should assign AngularJS to the global variable angular. This is done by using the attribute shim. In order to achieve the correct initialization order, we define the AMD module angular as a dependency of the AMD module angularRoute.

Listing 7.21: AngularJS configuration in the context of RequireJS

require.config({
  paths: {
    angular: '../bower_components/angular/angular',
    angularRoute: '../bower_components/angular-route/angular-route'
},
shim: {
  'angular' : {'exports' : 'angular'},
  'angularRoute': ['angular']
  }
});

You can load libraries through a content delivery network (CDN). The aforementioned RequireJS convention for modules are also important at this point. You can even specify a fallback (http://requirejs.org/docs/api.html#pathsfallbacks). This way, you can specify a CDN resource as the first parameter and a local resource as an alternative to be loaded in the case the CDN is not reachable. This is presented in Listing 7.22.

Listing 7.22: AngularJS configuration in the Context of RequireJS with a CDN and a fallback

require.config({
  [...]

  paths: {
  angular: [
    '//ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular',
    '../bower_components/angular/angular'
  ],

  [...]
});

There are other configuration options in RequireJS. Is is advisable to spend time to study its online documentation at http://requirejs.org/docs/api.html.

Listing 7.23: Definition of the AMD Module app

define([
  'angular',
  'angularRoute'
], function (angular) {
  return angular.module('bmApp', ['ngRoute']);
});

When we use our AngularJS application together with RequireJS, we must make sure that we define bootstrapping from AngularJS only when all scripts have been loaded. We can use the require() function from RequireJS to manage manual bootstrapping correctly (See Listing 7.24).

Listing 7.24: Manual bootstrapping of our AngularJS application in the context of RequireJS

// requireConfig.js
[...]
require([
  'angular',
  'app',
  'config/routes'
], function(angular, app) {
  angular.element().ready(function() {
    var elem = document.getElementsByTagName('html')[0];
    angular.bootstrap(elem, [app['name']]);
  });
});

In this function we can listen to the DOMConteentLoaded event by registering a callback function with ready(). As soon as this event is raised, we can begin the initialization of the AngularJS application in the callback function. To this end, we use the function angular.bootstrap(). As a first parameter, we specify the DOM element, which we annotate with the ngAPP directive in the event of automatic initialization. The second parameter defines the name of the application module that should be loaded. We can export the name of the application module from the AMD module app, which is available at this time.

In Listing 7.24, we specify the AMD module config/routes as a dependency. In this module, we define the routes of our AngularJS application. Listing 7.25 presents the module definition.

Listing 7.25: The route configuration of our AngularJS application as an AMD module

define([
  'app',
  'controllers/book_list',
  'controllers/book_details'
], function (app) {
  app.config(function($routeProvider) {
    $routeProvider.when('/books/:isbn', {
      templateUrl: 'templates/book_details.html',
      controller : 'BookDetailsCtrl'
    })
    .when('/books', {
      templateUrl: 'templates/book_list.html',
      controller : 'BookListCtrl'
    })
    .otherwise({
      redirectTo: '/books'
    });
  });
});

The AMD module config/routes requires three dependencies. First, it needs our AMD module app in order to provide the $routeProvider through the AngularJS config() function and configure the routes. Since we have to make sure that a specific controller has been loaded in the route configuration, we specify the AMD module in which we define the controller as a dependency. We can thus make sure that the two AMD modules controllers/book_list and controllers/book_details are both available.

Listing 7.26: An AngularJS controller as an AMD module in the context of RequireJS

define([
  'app',
  'services/book_data'
], function (app) {
  app.controller('BookListCtrl', function ($scope, BookDataService)
    {
    $scope.getBookOrder = function (book) {
      return book.title;
    };
    $scope.books = BookDataService.getBooks();
  });
});

As usual, we add a new AMD module to the define() function. In this case, we are dealing with an AMD module controllers/book_list. We also have a dependency on our app module so that we can add the controller as a definition. Since this controller also requires the BookDataService, we should also specify the corresponding AMD module services/book_data as a dependency. As usual, AngularJS internal mechanisms such as dependency injection are useable in the module definition. AngularJS can specify the scope ($scope) as well as the BookDataService without any problems.

For the sake of completeness, we show in Listing 7.27 another summary from the definition of the AMD module services/book_data for the BookDataService. It is worth mentioning that we are can also embed the AMD module angular and use the global API from AngularJS (e.g. angular.element(), angular.copy() etc.).

Listing 7.27: An AngularJS service factory as an AMD module in the context of RequireJS

define([
  'angular',
  'app'
], function (angular, app) {
  app.factory('BookDataService', function () {
    // angular.element()
    // angular.copy()
    // ...
  });
});

Therefore, we have shown how you can execute an AngularJS application in the context of RequireJS. As a summary, the dependencies of our application are shown in Figure 7.3. The diagram is not fully accurate technically, because we require our AMD module app in every other module. However, this would make the diagram very difficult to read. We have thus left these dependency relationship out of the diagram.

Testing with RequireJS

In this section, we look at the steps that are necessary in order for us to use an AngularJS application in the context of RequireJS. First, we have to install the RequireJS Karma adapter with npm. We also have to specify in our Karma configuration under frameworks. The installation of the Karma adapter is done with the following command in the command line:

npm install karma-requirejs

Figure 7.3: Module dependencies in BookMonkey in the context of RequireJS

When we have completed that, we have to adjust the files block in the Karma configuration.

Since RequireJS embeds the files and reloads them asynchronously itself, we have to provide the required files in the test suite without directly embedding them. We achieve this by setting the attribute included to false (see Listing 7.28). We can thus define that the web server that Karma started is allowed to send the files without embedding it using a <script> tag. Karma was explained in Chapter 5.

Listing 7.28: Karma configuration for testing an AngularJS application in the context of RequireJS

// frameworks to use
frameworks: ['jasmine','requirejs'],

// list of files / patterns to load in the browser
files: [
{
  pattern: 'app/bower_components/angular/angular*.js',
  included: false
},
{
  pattern: 'app/bower_components/angular-route/angular-route*.js',
  included: false
},
{
  pattern: 'app/bower_components/angular-mocks/angular-mocks*.js',
  included: false
},
{
  pattern: 'app/scripts/**/*.js',
  included: false
},
{
  pattern: 'test/unit/**/*.js',
  included: false
},

'test/testRequireConfig.js'

],

[...]

In addition, we have to specify a RequireJS configuration for our test that is adapted to execution in Karma (see Listing 7.29). In doing so, we note the following points:

  • AngularJS must be included with all the required modules.
  • The AngularJS module ngMock must be included.
  • The path configuration must be adjusted for Karma.
  • The baseUrl must be adjusted.
  • Our tests must be specified as dependencies.
  • We have to define a callback function that starts the test.

Since Karma delivers the loaded files under the path base, we have to watch out for this in the definition of our module. We should also adjust the path entries accordingly.

In addition, we group our tests in an array named tests. In the process, we iterate over all the files Karma has loaded to find a file that matches spec.js.

Listing 7.29: The testRequireConfig.js file for testing our AngularJS application in the context of RequireJS

// we get all the test files automatically
var tests = [];
for (var file in window.__karma__.files) {
  if (window.__karma__.files.hasOwnProperty(file)) {
    if (/spec.js$/i.test(file)) {
      tests.push(file);
    }
  }
}

require.config({
  paths: {
    angular: '/base/app/bower_components/angular/angular',
    angularRoute:
      '/base/app/bower_components/angular-route/angular-route',
    angularMocks:
      '/base/app/bower_components/angular-mocks/angular-mocks'
  },

  baseUrl: '/base/app/scripts/',
  shim: {
    'angular' : {'exports' : 'angular'},
    'angularRoute': ['angular'],
    'angularMocks': {
      deps:['angular'],
      'exports':'angular.mock'
    }
  },
  deps: tests,
  callback: window.__karma__.start
});

When we are done with this, we can define our tests as AMD modules. As always, in this module we can work with a test framework of our choice (e.g. Jasmine).

The AMD module in Listing 7.30 shows some tests for our DataBookService.

Listing 7.30: An AMD Module with test cases for the BookDataService

define([
  'angular',
  'angularMocks',
  'services/book_data'
], function (angular, mock) {
   describe('Service: BookDataService', function () {
      var BookDataService;

    // load the application module
    beforeEach(module('bmApp'));
    
    // get a reference to the service
    beforeEach(inject(function (_BookDataService_) {
      BookDataService = _BookDataService_;
    }));

    describe('Public API', function () {
      it('should include a getBookByIsbn() function', function () {
        expect(BookDataService.getBookByIsbn).toBeDefined();
      });

      it('should include a getBooks() function', function () {
        expect(BookDataService.getBooks).toBeDefined();
      });
    });
  });
});

Testing an AngularJS application in conjunction with RequireJS initially requires a little more effort. However, after the early hurdles, you can define test cases as usual by providing them in the form of AMD modules.

The Answer

As you have just seen, it is possible to implement AngularJS with RequireJS. Unfortunately, RequireJS’s biggest advantage–the asynchronous reloading of modules–is canceled out by the fact that every component (controllers, services, etc.) must be available at the start of the application for AngularJS. The problem also exists when embedding other AngularJS modules. Our application module’s dependencies also have to be available and thus cannot be asynchronously reloaded.

Another problem for the combination of AngularJS and RequireJS is the fact that we cannot execute our E2E tests. The reason for this is that the ngScenarioDSL from AngularJS can only be implemented when we start the application with the ngApp directive. Since a manual Bootstrapping must be undertaken in combination with RequireJS, the condition for an implementation of ngScenario is no longer extant.

The mix of AngularJS and RequireJS can nevertheless be interesting. Because we attach every piece of information in RequireJS to its module definition via its dependencies and only have to work with one <script> tag, we do not have to manually make sure that the individual application components are embedded in the correct order. When using RequireJS, the order is provided to the dependencies implicitly by the meta information. With a clever build process, however, embedding order issue can be resolved without RequireJS.

Based on our experience, we do not recommend that you use RequireJS and AngularJS together. Instead, we recommend solving the loading order problem using an optimized building process. The reason for this is the fact that E2E tests can no longer be executed in connection with RequireJS. Because of this, an important mechanism for quality maintenance is no longer available. This is an inadequate ascertainment for large development teams, in which employees with the knowledge of the requirements create user story scenarios and developers program these in the form of E2E tests.

In addition, the source code is not so promising without the definition from AMD modules. It is therefore more difficult to understand. New developers that are not familiar with JavaScript or AngularJS must further understand another complex library in order to find a connection in a project when implementing RequireJS. The number of JavaScript developers who can work well in the entire ecosystem of language and thus know all of these frameworks is not very high.

Summary

  • RequireJS is a JavaScript framework that enables the asynchronous loading of files and modules. It implements the asynchronous module definition (AMD) API.
  • By using a small example, we introduced the function and definition of AMD modules.
  • We showed how AngularJS can be implemented in conjunction with RequireJS and what the implications are.
  • We discussed the steps necessary to test applications that use AngularJS and RequireJS together.
  • We generally advise against using AngularJS with RequireJS because this combination has many disadvantages.

Mobile Devices: Does AngularJS Support Mobile Devices?

Because of the increasing trend towards the use of mobile devices on the web, many wonder whether AngularJS is specially compatible with such devices.

In addition to the popular techniques for optimizing an application for mobile devises (e.g. Responsive Web Design) which can be implemented independently from AngularJS, AngularJS offers several extensions that were specially developed for mobile applications. Thus, we can answer the above question in good conscience with a “yes”.

Supporting Touch Events

AngularJS comes with the ngTouch module, which contains extensions for interacting with touch devices. ngTouch is not a part of the core library. If you want to use it, you have to download and embed it with a <script> tag. If you are not using Bower, you can download the module at http://code.angularjs.org/ to manage the front end dependencies. In addition, you have to specify it as a dependency when defining your application module (see Listing 7.31).

Listing 7.31: Embedding the ngTouch Module.

angular.module('app', ['ngTouch']);

When you embed the ngTouch module, the ngClick directive is replaced with an alternative implementation, which is designed for touch interactions. The reason for this is that most browsers send a click event to the document first after 300 milliseconds after the tap and release event. Thus, click interaction appears to be slow when using the ngClick directive on mobile devices. On the other hand, the alternative implementation from the ngTouch module reacts immediately and the application thus appears to be much smoother. In addition, it suppresses the native browser event in order to avoid duplication.

You can test this behavior by using the ngClick directive in a simple template and call up the application with a touch enabled device (see Listing 7.32).

Listing 7.32: Using ngClick in the ngTouch module

<!DOCTYPE html>
<html ng-app="app">
<body>
  <div ng-click="click=click+1" ng-bind="click"></div>

  <!-- Scripts -->
  <script src="lib/angular/angular.min.js"></script>
  <script src="lib/angular-touch/angular-touch.min.js"></script>
  <script src="scripts/app.js"></script>
</body>
</html>

The Swipe Directives

Another extension that comes with the ngTouch module is the support for swipe gestures. A swipe occurs when you stroke over a touch screen with one or more fingers. This gesture is often implemented to show a context menu for an element.

The implementation selected in AngularJS is based on jQuery Mobile. jQuery Mobile is not required as a dependency because ngTouch contains the relevant parts of the library.

The module comes with two directives for swipe gestures. The ngSwipeLeft directive deals with swipe gestures directed to the left, while the ngSwipeRight directive deals with swipes to the right.

Listing 7.33 shows the management of these directives.

Listing 7.33: Using ngSwipeLeft and ngSwipeRight

<!DOCTYPE html>
<html ng-app="app">
<body>
  <div ng-swipe-right="right=right+1" ng-swipe-left="left=left+1">
    <div>Swipe-right: {{right}}</div>
    <div>Swipe-left: {{left}}</div>
  </div>

  <!-- Scripts -->
    <script src="lib/angular/angular.min.js"></script>
    <script src="lib/angular-touch/angular-touch.min.js"></script>
    <script src="scripts/app.js"></script>
</body>
</html>

The $swipe Service

If you want to implement more complex swipe interactions or have more control over what a swipe does, you cannot do that with ngSwipeLeft or ngSwipeRight. For this reason, the ngTouch module contains the $swipe service, which uses both directives internally. With it you can process the swipe events on a lower level.

The public API from this service includes a function called bind for listening to events. The different event types supported are listed in Table 7.1.

Type

Description

Activated by

start

Start of the interaction

mousedown, touchstart

move

The current position during interaction

mousemove, touchmove

end

End of interaction

mouseup, touchend

cancel

Occurs when the current interaction is canceled

touchcancel

Table 7.1: Events supported by the $swipe service

The function bind() takes as the first parameter a reference to a DOM element that should be monitored for swipe events.

As the second parameter, you specify a configuration object that defines callback functions for the events in Table 7.1. The callback functions for the start, move and end events return an object containing the swipe coordinates. As such, you can access the current x and y positions of the finger movement. In addition, during a callback, the service provides access to the native browser element that was activated by the swipe event. Listing 7.34 shows a sample bind() function.

Listing 7.34: A callback for the bind() function of the $swipe service

$swipe.bind(element, {
  'start' : function(coords, event) {
    console.log('start', coords.x, coords.y);
  },
  'move' : function(coords, event) {
     console.log('move', coords.x, coords.y);
  },
  'end' : function(coords, event) {
    console.log('end', coords.x, coords.y);
  },
  'cancel': function(event) {
    console.log('cancel', event);
  }
});

As an example, we use the $swipe service to create a directive that allows us to drag an element, i.e. to move it by using a finger gesture. Such an object would be said to be draggable, so we name our directive draggable.

Since we want to use this behavior on existing elements, we create a directive that we can implement as an HTML attribute (see Listing 7.35). In the link function, we can create the desired functionality by using a callback to $swipe.bind(). To this end, we use the move event and set the position of the element by using the attributes top and left. In doing so, we make sure that the CSS attribute position is set relative to its parent container. The element that is annotated by the directive must have this CSS attribute. However, for the element the value is absolute.

Listing 7.35: Our Draggable directive based on the $swipe service

angular.module('app').directive('draggable',
function ($swipe) {
  return {
    restrict: 'A',
    link: function (scope, element) {
      var width = element[0].offsetWidth,
        height = element[0].offsetHeight;

      var toggleActive = function () {
        element.toggleClass('swipe-active');
      };

      $swipe.bind(element, {
        'start': toggleActive,
        'move': function (coords) {
          element.css('left', coords.x-(width/2) + "px");
          element.css('top', coords.y-(height/2) + "px");
        },
        'end': toggleActive,
        'cancel': toggleActive
      });
    }
  };
});

In addition, we define a toggleActive() function to set the CSS class swipe-active on the element as soon as this gesture is active. This way, we can visualize this state better with a frame or transparent.

Now we can embed this directive in our example by creating an element that is annotated with the HTML attribute draggable (see Listing 7.36).

Listing 7.36: Using our draggable directive

<!DOCTYPE html>
<html ng-app="app">
<head>
  <meta charset="utf-8">
  <title>Usage of Draggable Directive</title>
  <link rel="stylesheet" href="css/draggable.css" media="screen">
</head>
<body>

  <div class="draggable" draggable></div>

  <div ng-click="click=click+1">
    <div>Click: {{click}}</div>
  </div>

  <div ng-swipe-right="right=right+1" ng-swipe-left="left=left+1">
    <div>Swipe-right: {{right}}</div>
    <div>Swipe-left: {{left}}</div>
  </div>

  <!-- Scripts -->
  <script src="lib/angular/angular.min.js"></script>
  <script src="lib/angular-touch/angular-touch.min.js"></script>
  <script src="scripts/app.js"></script>
  <script src="scripts/directives/draggable.js"></script>
</body>
</html>

Summary

  • In this section, we discussed the extent to which AngularJS supports mobile devices.
  • By using the ngTouch module, you can access extensions that AngularJS provides for mobile devices. It comes with a touch optimized implementation of the ngClick directive and also contains directives for dealing with swipe events.
  • With the $swipe service, which is a part of ngTouch, you can implement touch functions such as drag and drop.
..................Content has been hidden....................

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