The Anatomy of an AngularJS App: Using Modules to Organize Code

In previous examples, I showed you how AngularJS uses dependency injection with factory functions when you create components such as controllers, filters, and services. Right at the start of the chapter, I explained that the second argument to the angular.module method, used to create modules, was an array of the module’s dependencies:

var myApp = angular.module(“exampleApp”, []);

Any AngularJS module can rely on components defined in other modules, and this is a feature that makes it easier to organize the code in a complex application. To demonstrate how this works, I have added a JavaScript file called controllers.js to the angularjs folder. You can see the contents of the new file in Listing 9-14.

Listing 9-14. The Contents of the controller.js File

var controllersModule = angular.module(“exampleApp.Controllers”, [])

controllersModule.controller(“dayCtrl”, function ($scope, days) {

$scope.day = days.today;

});

controllersModule.controller(“tomorrowCtrl”, function ($scope, days) {

$scope.day = days.tomorrow;

});

In the controllers.js file I created a new module called exampleApp.Controllers and used it to define the two controllers from earlier examples. One common convention is to organize your application into modules that have the same type of component and to make it clear which building block a module contains by using the main module’s name and appending the block type, which is why it’s called exampleApp.Controllers. Similarly, I have created a second JavaScript file called filters.js, the contents of which are shown in Listing 9-15.

Listing 9-15. The Contents of the filters.js File

angular.module(“exampleApp.Filters”, []).filter(“dayName”, function () {

var dayNames = [“Sunday”, “Monday”, “Tuesday”, “Wednesday”,

“Thursday”, “Friday”, “Saturday”]; return function (input) {

return angular.isNumber(input) ? dayNames[input] : input;

};

});

I have created a module called exampleApp.Filters and used it to create the filter that was part of the main module in earlier examples. For variety, I have used the fluent API to call the filter method directly on the result of the module method (see the “Using the Fluent API” sidebar earlier in the chapter for details).

Tip There is no requirement to put modules in their own files or to organize modules based on the building blocks they contain, but it is the approach I generally prefer and is a good place to start while you are figuring out your own AngularJS development processes and preferences.

In Listing 9-16, you can see how I have added script elements to import the controllers.js and filters.js files and added the modules they contain as dependencies to the main exampleApp module. I have also created two further modules within the example.html file to emphasize that modules don’t have to be defined in their own files.

Listing 9-16. Using Module Dependencies in the example.html File

<!DOCTYPE html>

<html ng-app=”exampleApp”>

<head>

<title>AngularJS Demo</title>

<link href=”bootstrap.css” rel=”stylesheet” />

<link href=”bootstrap-theme.css” rel=”stylesheet” />

<script src=”angular.js”></script>

<script src=”controllers.js”x/script>

<script src=”filters.js”></script>

<script>

var myApp = angular.module(“exampleApp”,

[“exampleApp.Controllers”, “exampleApp.Filters”,

“exampleApp.Services”, “exampleApp.Directives”]);

angular.module(“exampleApp.Directives”, [])

.directive(“highlight”, function ($filter) {

var dayFilter = $filter(“dayName”);

return function (scope, element, attrs) {

if (dayFilter(scope.day) == attrs[“highlight”]) {

element.css(“color”, “red”)j

}

}

});

var now = new Date();

myApp.value(“nowValue”, now);

angular.module(“exampleApp.Services”, [])

.service(“days”, function (nowValue) {

this.today = nowValue.getDay();

this.tomorrow = this.today + 1;

});

</script>

</head>

<body>

<div class=”panel”>

<div class=”page-header”>

<h3>AngularJS App</h3>

</div>

<h4 ng-controller=”dayCtrl” highlight=”Monday”>

Today is {{day || “(unknown)” | dayName}}

</h4>

<h4 ng-controller=”tomorrowCtrl”>

Tomorrow is {{day || “(unknown)” | dayName}}

</h4>

</div>

</body>

</html>

To declare the dependencies for the main module, I add each module’s name to the array I pass as the second argument to the module, like this:

var myApp = angular.module(“exampleApp”, [“exampleApp.Controllers”, “exampleApp.Filters”,

“exampleApp.Services”, “exampleApp.Directives”]);

The dependencies don’t have to be defined in any particular order, and you can define modules in any order as well. For example, I define the exampleApp.Services module in the listing after I have defined the exampleApp module that depends on it.

AngularJS loads all the modules that are defined in an application and then resolves the dependencies, merging the building blocks that each contains. This merging is important because it allows for the seamless use of functionality from other modules. As an example, the days service in the exampleApp.Services module depends on the nowValue value in the exampleApp module, and the directive in the exampleApp.Directives module relies on the filter from the exampleApp.Filters module.

You can put as much or as little functionality in other modules as you like. I have defined four modules in this example but left the value in the main module. I could have created a module just for values, a module that combined services and values, or a module with any other combination that suited my development style.

1. Working with the Module Life Cycle

The Module.config and Module.run methods register functions that are invoked at key moments in the life cycle of an AngularJS app. A function passed to the config method is invoked when the current module has been loaded, and a function passed to the run method is invoked when all modules have been loaded. You can see an example of both methods in use in Listing 9-17.

Listing 9-17. Using the config and run Methods in the example.html File

<script>

var myApp = angular.module(“exampleApp”,

[“exampleApp.Controllers”, “exampleApp.Filters”,

“exampleApp.Services”, “exampleApp.Directives”]);

myApp.constant(“startTime”, new Date().toLocaleTimeString());

myApp.config(function (startTime) {

console.log(“Main module config: ” + startTime);

});

myApp.run(function (startTime) {

console.log(“Main module run: ” + startTime);

});

angular.module(“exampleApp.Directives”, [])

.directive(“highlight”, function ($filter) {

var dayFilter = $filter(“dayName”);

return function (scope, element, attrs) {

if (dayFilter(scope.day) == attrs[“highlight”]) {

element.css(“color”, “red”);

}

}

});

var now = new Date(); myApp.value(“nowValue”, now);

angular.module(“exampleApp.Services”, [])

.service(“days”, function (nowValue) {

this.today = nowValue.getDay();

this.tomorrow = this.today + 1;

})

.config(function() {

console.log(“Services module config: (no time)”); })

.run(function (startTime) {

console.log(“Services module run: ” + startTime); });

</script>

My first change in this listing is to use the constant method, which is similar to the value method but creates a service that can be declared as a dependency by the config method (which you can’t do when you create values).

The config method accepts a function that is invoked after the module on which the method is called is loaded. The config method is used to configure a module, usually by injecting values that have been obtained from the server, such as connection details or user credentials.

The run method also accepts a function, but it will be invoked only when all of the modules have been loaded and their dependencies have been resolved. Here is the sequence in which the callback functions are invoked:

  1. The config callback on the Services module
  2. The config callback on the exampleApp module
  3. The run callback on the Services module
  4. The run callback on the exampleApp module

AngularJS does something clever, which is to ensure that modules on which there are dependencies have their callbacks invoked first. You can see this in the way that the callbacks for the exampleApp.Services module are made before those for the main exampleApp module. This allows modules to configure themselves before they are used to resolve module dependencies. If you run the example, you will see JavaScript console output like the following:

Services module config: (no time)

Main module config: 16:57:28

Services module run: 16:57:28

Main module run: 16:57:28

I am able to use the startTime constant in three of the four callbacks, but I can’t use in the config callback for the exampleApp.Services module because the module dependencies have yet to be resolved. At the moment the config callback is invoked, the startTime constant is unavailable.

Source: Freeman Adam (2014), Pro AngularJS (Expert’s Voice in Web Development), Apress; 1st ed. edition.

Leave a Reply

Your email address will not be published. Required fields are marked *