This is a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. You can read more about the project at http://henriquat.re. You can also follow us on twitter (Project: @henriquatreJS, Authors: @ingorammer and @christianweyer)

 

Modularizing AngularJS Applications

When talking about modularizing applications built on AngularJS, there are two independent elements to talk about: splitting the code into logical modules on one hand, and into multiple physical files on the other hand.

In this chapter, we will start with the first one - the logical organization of source code. The reason for this is that AngularJS itself does not mandate - or even prescribe - a particular organization of code files. We'll look at the different options you'll have for organizing your application's source code a bit later in Basics of Single-Page Applications with ASP.NET MVC (Please note: this chapter has not yet been released).

As you've seen in previous chapters, AngularJS allows (or even requires) the definition of modules using angular.module(). We haven't talked much about this in previous chapters, but a module is essentially a configuration for Angular's dependency injector which allows you to group a set of controllers, directives, filters, etc. under one particular name. Each module can also specify other module on which it depends so that this allows you to re-use and structure your code. Angular also automatically adds its default modules, ng and ngLocale which provide core functionality to your applications.

In the following fragment, you can see that root DIV of this application specifies the name of its application-module in ng-application="myApplication". When Angular sees this markup during its bootstrapping process, it will try to locate the configured module.

<div ng-app="myApplication" ng-controller="DemoController">
    Some content ...
    <div my-directive="testing">
        Some more content ...
    </div>
</div>

This application also tries to reference a directive called my-directive. In the first iteration of this example, this directive is however not part of our application:

var myApplication = angular.module("myApplication", []);

function DemoController() {
}

It is important to realize that, when running this application, AngularJS does not complain about the missing directive. Instead, the HTML is just rendered as-is in the clients browser. As the browser also does not know how to deal with the my-directive attribute, it will just be silently ignored.

You could now define a second module and add the my-directive directive to it as shown in the following fragment. This directive simply transcludes its content into a DIV with a red background.

var mySharedElements = angular.module("mySharedElements", []);

mySharedElements.directive("myDirective", function () {
   return {
      restrict: "A",
      transclude: true,
      template: "<div style='background-color:red' ng-transclude></div>",
   };
});

But even after applying this change and running the application again, you would still see the same result as above: the directive would not be rendered. The reason is that AngularJS has no way yet to know that you want the components from the module mySharedElements to be used from within the module myApplication. To add this final link, you need to add mySharedElements to the list of dependencies for your application module, as you can see in the following fragment.

var myApplication = angular.module("myApplication", ["mySharedElements"]);

After this change, the directive will be rendered as expected:

Fluent Definition of Modules

In the previous code snippets, we have usually assigned the return value of angular.module() to a variable and then used the variable to register additional components for the module.

This is not 100% necessary - and actually not normally used in larger applications - as it pollutes the global namespace. A second disadvantage of writing code like the one you've seen until now is that the same is true for the controller constructors: they also become part of the global namespace.

To work around these issues, Angular provides the following solution: on one hand, controllers can also be defined to be scoped to a certain module. To do so, you would simply define the controller as part of a module using someModule.controller(). To avoid polluting the global namespace with the module itself, all of the module's method are chainable and return a reference to the module itself.

Instead of writing code like this:

var mod = angular.module("foo", []);
mod.directive("directiveA", function() { /* ... */ });
mod.directive("directiveB", function() { /* ... */ });
mod.directive("directiveC", function() { /* ... */ });

you could therefore simply used the chained syntax like this:

angular.module("foo", [])
   .directive("directiveA", function() { /* ... */ })
   .directive("directiveB", function() { /* ... */ })
   .directive("directiveC", function() { /* ... */ });

The drawback of this approach - at least at the first glance - is that it appears as if you'd have to defined all parts of your module in one source file. This is usually not what you'd like to do in any larger application. Angular therefore allows you to re-open a module definition by using angular.module("name"). This function will return a reference to a previously defined module.

Just keep in mind that you must only specify the dependency-array on the first call to module as you would otherwise redefine the module's dependency list instead of adding additional parts to an existing module.

Basically, you define a module with:

angular.module("name", ["dependencyA", "dependencyB"])

and re-open it (to add further parts to it) with:

angular.module("name")

If you combine all these parts with the previous example, you would end up with the following definition for the 'reusable' module mySharedElements, which provides a directive which is 100% independent of a particular application.

angular.module("mySharedElements", [])
   .directive("myDirective", function () {
      return {
         restrict: "A",
         transclude: true,
         template: "<div style='background-color:red' ng-transclude></div>",
      };
   });

You could then define an application module in one part of your code:

angular.module("myApplication", ["mySharedElements"]);

And add a controller to the module in - presumably - another code file:

angular.module("myApplication").controller("DemoController", function () {
   // here comes some controller code ...
});

When running this application, the directive would act exactly like before. But this time you've achieved this goal with a clear separation between the modules, without any unnecessary pollution of the global namespace and with the ability to even define the controller in a source file other than the one which creates the module.

Group Modules by Functionality, Not By Type

Given that you now know that you can split your application into different modules, your next question might be: what's the best boundary for this split? Should you split your application into separate parts for directives, for controllers, filters, etc? Or should you group your modules based on functionality?

The general consensus - shared at some conference talks by members of the AngularJS team - is fortunately quite clear: it's best to group your source-code into modules by functionality. This allows you to create re-usable components at the base of your application (or even at the base of multiple applications), to re-use existing modules which have been created by others, and then to add modules which group functional parts of your application.

If you would for example write an email application, you might end up with the following elements:

  • RichEditor: a generic, email-independent collection of directives, helper services and controllers for allowing the user to edit and format text in their browser
  • Folder: directives and services for displaying a folder's content
  • Inbox: directives, services and controllers for displaying special elements for the user's inbox
  • Compose: components for composing a new mail, which in turn uses RichEditor
  • EmailApplication: the top-level application module which is referenced in your HTML's root DIV (in ng-app="EmailApplication");

Your module dependencies would therefore look like the one shown in the following figure.

In code, you would end up with a representation similar to the following (when omitting the real contents of the individual modules):

angular.module("RichEditor", []);

angular.module("Folder", []);

angular.module("Inbox", ["Folder"]);

angular.module("Compose", ["RichEditor"]);

angular.module("EmailApplication", ["Inbox", "Compose"]);

Summarizing Modules

The important element in this example is that the modules RichEditor and Folder are totally independent of each other. They can be specified, developed, tested and deployed without knowing about each other. Only EmailApplication, via its dependencies to Compose and Inbox, brings these parts together for the first time.

This is very important: it allows JavaScript applications do be developed using a scalable process which allows independent developers or teams to focus on independent parts of an application.

 

This was a preview-quality chapter of our continuously deployed eBook on AngularJS for .NET developers. If you enjoyed this chapter, you can read more about the project at http://henriquat.re. You can also follow us on twitter (Project: @henriquatreJS, Authors: @ingorammer and @christianweyer)