Generally speaking, the goal of modules is to achieve a separation of concerns by defining public APIs and limiting the visibility of behavior (functions) and data (properties and variables). Most programming platforms include built-in support for modules, making their use something to take for granted. Client-side JavaScript currently does not, creating a situation in which debates rage over the pros and cons of the popular add-on solutions, such as CommonJS and Asynchronous module definition (AMD).

Keep in mind when making comparisons to other solutions that Angular's module system is not a module loader. It does not load source code from files or the internet for you. What Angular provides is a built-in, proprietary module solution that works closely with its similarly built-in and proprietary dependency injection solution. Together, the two systems provide an impressive amount of general-purpose infrastructure. Although learning to use this functionality is within the ability of any developer, the question is, why do we need it?

Modules and dependency injection are ordinarily an "all-in" proposition, but as we have seen, Angular allows us to get started without using its module system. So far, we have been able to accomplish quite a lot simply by defining controllers as named functions on the global scope. This works because with a little configuration, the ng-controller directive searches for controller functions globally as well as in the module system. The less-complicated global approach to using Angular is permitted, but only up to a point.

Why use Angular modules?

In order to move beyond Angular's basic features, you will need to master its module system. This book does not seek to convince you of the intrinsic value of Angular's modules or dependency injection. When it comes to the general problem of managing complexity in your JavaScript projects, you should arrive at your own best practices, and a simpler approach may be better. This book covers modules and dependency injection as necessary to use Angular.

Therefore, let's survey the important features that require us to use the module system:

  • Specialized components - To define your own controllers, directives, filters, and animations, you must use the module system. (Controllers may be excepted, as mentioned above.)
  • Dependency injection - Although services are just ordinary JavaScript objects and functions, only services created with the module system can be easily injected with their dependencies, as well as themselves made available for dependency injection.
  • External modules - There is an impressive ecosystem of free, open-source Angular add-ons, both from the core team and third parties. To use any of these libraries in your application, you must use Angular's module system.
  • Load-time configuration - Angular's module system provides access to lifecycle hooks into Angular's internal configuration, as well as the configuration of add-on modules. The latter will be demonstrated at the end of the final chapter when we configure a custom HTTP header.
  • Testing - Angular's impressive testing infrastructure leverages its dependency injection system, which in turn depends on its module system.

An upcoming chapter, Services, will demonstrate how to define providers for custom JavaScript components. This chapter will show how to define a controller as well as to load external modules at the same time that it teaches you the basics of the module system.

Creating the application module

An Angular application can be initialized with only one root module. In our examples this module will be named app by convention. This name isn't significant, but it is commonly seen in much of Angular's documentation.

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

In the module.js example above, please take careful notice of the second argument to the module method. Although it is just an empty, innocent-looking array ([]) in this usage, the presence of this second argument is extremely important. If you omit it, the behavior of angular.module changes radically. Talk about questionable API design! When you call angular.module with the second argument, the function operates in create mode, and creates a new module named app if no module with that name already exists. However, if you call angular.module without the second argument, it operates in lookup mode. If a module with the given name already exists, it will return it; if not, it throws an error that helpfully mentions including the second argument. We've got it right in this example, but just be aware of this important difference in your own code. As for the array argument, it is used to import External modules, which we will discuss presently, using Angular's animation module for our example.

Loading the application module

In the HTML document that hosts our application, we can instruct Angular to load the application module by passing its name to the ng-app directive.

<body ng-app="app">
  <!-- Other examples to be inserted here. -->
</body>

Any components that we add to our application module will be available for use. Let's see what it looks like to define a controller within a module rather than on the global scope.

Defining a controller

The API for module includes functions for defining specialized Angular components. The only specialized component that we are familiar with so far is the controller, so let's use the controller function to create one. First we need to obtain a reference to the application module. As explained above, we must call angular.module without its second argument, so that it operates in lookup mode.

var app = angular.module('app');

app.controller('MessageController', function($scope) {
  $scope.message = "This is a model.";
});

Now, somewhere within the element on which we invoked ng-app="app" , we can use ng-controller to load our controller.

<p ng-controller="MessageController"> {{message}} </p>

As you can see, defining a controller within a module is a little more work than defining one on the global scope, but not too bad at all.

Chaining definitions

Let's say that we need to define two controllers for the template below.

<p ng-controller="MessageController"> {{message}} </p> <p ng-controller="AnotherMessageController"> {{message}} </p>

The definition functions on Module are chainable, so that we can create two controllers in a single statement.

angular.module('app')
  .controller('MessageController', function($scope) {
    $scope.message = "This is a model.";
  })
  .controller('AnotherMessageController', function($scope) {
    $scope.message = "This is another model.";
  });

Notice that the first call to controller is not terminated with a semicolon.

If you don't like the chaining style, you can call module as often as you like to retrieve the module reference, or else store your module in a variable, as shown in the next example. If you use a variable, it is good to place it within an IIFE or other closure to prevent it from winding up on the global scope.

var app = angular.module('app');

app.controller('MessageController', function($scope) {
  $scope.message = "This is a model.";
});

app.controller('AnotherMessageController', function($scope) {
  $scope.message = "This is another model.";
});

How you choose to do it is up to you, but the chaining style is popular.

Loading modules

Animations is a newer Angular feature that was added as a separately packaged module named ngAnimate. The first step to using animations is to include the JavaScript file containing the module. The source code for the module is part of the core Angular project, but it is distributed as a separate file, as shown below.

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.js"></script>

It's time to really use the module function's second array parameter, to which we passed an empty array in the earlier example. This is where we can declare our module dependencies.

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

We have loaded ngAnimate into our application module. In our templates, the directives ng-show, ng-hide, ng-class, and several others will now check for both CSS and JavaScript animations that can be applied to DOM events like add, enter, leave, move, and remove. Applying animations to supported DOM events is transparent in the sense that no changes are required to our template code. If no animation code is present, either as CSS or as JavaScript registered via module.animation, our directive code will behave normally.

<input type="checkbox" ng-model="showMessage">
Check the box to show the message
<h2 ng-show="showMessage">
  The secret message.
</h2>

Clicking the checkbox above just does what ng-show normally does, which is to immediately switch on or off the visibility of the element. There will be no timed fading until we write animation code in CSS or JavaScript. For this example we will use CSS, which is generally the better choice for common animations due to its declarative style. However, there is nothing wrong with using jQuery animate to get a job done when it provides the easier path, and Angular accommodates this approach with module.animation.

Controlling the fading requires the use of four hook CSS classes, as noted in the API documentation for ng-show. These are ng-hide-add, ng-hide-add-active, ng-hide-remove, and ng-hide-remove-active.

.ng-hide-add,
.ng-hide-remove {
  transition: all linear 1s;
  display: block !important;
}

.ng-hide-add.ng-hide-add-active,
.ng-hide-remove {
  opacity: 0;
}

.ng-hide-add,
.ng-hide-remove.ng-hide-remove-active {
  opacity: 1;
}

Depending on your site's users, you may need to add vendor-prefixed properties for certain browsers, such as -webkit-transition for iOS Safari 6. (The site caniuse.com is a good reference for browser compatibility with HTML5 and CSS3.) Please also note that as written, this animation will be applied to every usage of ng-show and ng-hide in our application. For better control, we can add a custom CSS class to both the element and the CSS selectors, for example .my-class.ng-hide-add, .my-class.ng-hide-remove, and so on.

Conclusion

I'm hoping that the ease with which we added ngAnimate convinces you of the importance of understanding the Angular module system. And happily, ngAnimate is just the beginning in terms of add-on modules for Angular. In addition to the other modules from the Angular project, such as (ngResource)[http://docs.angularjs.org/api/ngResource] and (ngRoute)[http://docs.angularjs.org/api/ngRoute], you can also choose from the quickly growing ecosystem of third-party open-source modules that are freely available on sites like GitHub. One of the most popular projects is AngularUI, which includes powerhouse modules such as UI Router and UI Bootstrap. Most of these popular modules can be easily installed into your project using Bower, the web package manager. Once you get the hang of Angular, you may be publishing your own open-source Angular modules to GitHub and Bower. I hope so!

The ngAnimate module provides a compelling demonstration of the power available through add-on modules, but because of its particular approach to integration, it does not involve the use of Angular's dependency injection infrastructure. Since modules and dependency injection typically work hand-in-hand, the next chapter will cover it.

I hope you have been enjoying Angular Basics.
You can join the mailing list to hear about updates to the book.
Please also let me know what you liked and what I can improve.
And please share the word using the social buttons below!