Saturday, April 26, 2014

The AngularJS Module System

When learning AngularJS, one of the biggest stumbling blocks was the large amount of new concepts thrown at me. It took a while to realize that half of these concepts had nothing to do with building single-page apps, or indeed websites, but rather was a system to work around a few deficiencies of JavaScript.

This set of features is what I’d call the AngularJS Module System, mainly because they’re centered around what AngularJS calls module. But AngularJS modules are not completely equivalent to modules in other languages.

This blog post assumes you have some knowledge of programming languages in general. It’s not a beginner’s introduction to JavaScript or AngularJS.

Modules

AngularJS modules are primarily used to order code execution according to dependencies.

A module has a name, dependencies on zero or more other modules, a config code block, and a run code block. When a module is required by AngularJS, it orders all modules in dependency chain according to their dependencies, and then first executes the config blocks of each module in order, and then executes the run blocks of each module in the same orders.

angular
  .module('foo', ['bar', 'baz'])
  .config(function () { ... })
  .run(function () { ... });
angular
  .module('bar', ['baz'])
  .config(function () { ... })
  .run(function () { ... });
angular
  .module('baz', [])
  .config(function () { ... })
  .run(function () { ... });

After this code has been loaded, requiring the foo module would execute config of baz, bar and foo, in that order, and then run of those modules in the same order. Requiring baz would only execute config and run of the baz module and ignore the others.

That’s the relevant part of AngularJS modules.

Dependency Injection

The next thing AngularJS introduces is dependency injection. This is the way AngularJS handles a way of importing values from other modules.

Usually in the JavaScript world, a module would simply create a global object and add its exports to that object. Other modules then would simply use the global object. This makes it difficult to test modules in isolation, as it does not make it clear what kind of modules they use. Dependency injection passes these exported values to modules as function arguments, making it a lot easier to test them in isolation.

In most places where AngularJS expects a function, it tries to inject registered values according to the argument specification.

["$provide", function ($provide) { ... }];
function ($provide) { ... };

Either one tells AngularJS that the function should be provided with whatever value has been registered under the name $provide. The only difference is that the latter doesn’t work after code minification, so the former is preferred. But for examples, the latter is shorter, so I’ll use that in further examples.

Incidentally, $provide is also how you register a value to be exported so it can be imported by others. The simplest way to do this is using constant.

angular
  .module('foo', [])
  .config(function ($provide) {
    $provide.constant('fnord', 5);
  });
angular
  .module('bar', ['foo'])
  .config(function (fnord) {
    // fnord will have value 5 here
  });

That’s the relevant part of AngularJS dependency injection.

Providers

AngularJS adds one more phase to this. Consider the situation of the built-in $log service. It can be used to log messages at various severity levels, for example info and debug. You can configure $log whether it should log debug messages or not.

Conceptually, that’s two different phases: First, when configuring your application, you want to say whether $log should emit debug messages. Only later do you want to actually start logging messages. AngularJS supports this separation using providers.

A provider is an object exported as fooProvider that is exported and available to subsequent config blocks. But once all config blocks are run, AngularJS goes through all the providers and calls the special method $get on them. The return value of this method then is registered under the name of foo (for fooProvider) for further calls.

(I’d argue this is unnecessary complexity for a minimal performance gain, but it’s quite possible that I don’t see the need yet. Regardless of whether it’s there or not, it’s what we have, so we better learn what it is about.)

angular
  .module('fooModule', [])
  .config(function ($provide) {
    var fooValue = 5;
    $provide.provider('foo', function () {
      return {
        setValue: function (newValue) {
          fooValue = newValue;
        },
        $get: function () {
            return fooValue;
        }
      };
    });
  });

Given this, when a module depends on fooModule, its config block can ask for a fooProvider to be passed as an argument. It can call the setValue method of that provider to change fooValue. After all config blocks are run, Angular registers a new value named foo with the value of fooValue, which is what fooProvider.get() returns. While config can use dependency injection to ask for providers, only the $get methods of providers can ask for the actual objects returned by $get methods of other providers.

All code run after the config blocks can depend on the actual values returned by $get, but each provider’s $get method will only ever be called once and its value stored.

That’s the relevant part of AngularJS providers.

Provider on Modules

Because this is quite a mouthful of code, AngularJS provides some shorthands for common idioms.

First, the whole config block is often unnecessary. The above could have easily been rewritten to use an object field instead of a function-local variable.

angular
  .module('fooModule', [])
  .config(function ($provide) {
    $provide.provider('foo', function () {
      return {
        fooValue: 5,
        setValue: function (newValue) {
          this.fooValue = newValue;
        },
        $get: function () {
          return this.fooValue;
        }
      };
    });
  });

And because this is rather common, AngularJS puts all the methods available on $provide directly on the module object, so you can skip the config call, making the following absolutely equivalent to the above.

angular
  .module('fooModule', [])
  .provider('foo', function () {
    return {
      fooValue: 5,
      setValue: function (newValue) {
        this.fooValue = newValue;
      },
      $get: function () {
        return this.fooValue;
      }
   };
  });

Factories

Next, it’s quite usual not to have any kind of methods on the provider at all, because not that many things need to be configured. You just have a $get method.

angular
  .module('fooModule', [])
  .provider('foo', function () {
     return {
       $get: function () {
         return 23;
       }
    };
   });

This is what AngularJS calls a factory, and it provides a factory method which makes the following code absolutely identical to the above.

angular
  .module('fooModule', [])
  .factory('foo', function () {
    return 23;
  });

Services

What if you want to simply instantiate a new object?

angular
  .module('fooModule', [])
  .factory('foo', function () {
    return new FooObject();
  });

This, too, happens regularly, and is what AngularJS calls a service.

angular
  .module('fooModule', [])
  .service('foo', FooObject);

There is a terminology conflict here. AngularJS also regularly calls the return value of a provider a service. The service created using the service method is just one kind of service. They know this is confusing and it sounds like they regret that choice, but it’s apparently too small a problem to fix. I’d call this method instance, but that’s me.

Values

Factories and services still allow you to use dependency injection to import values from other modules. Sometimes, you do not even need that.

angular
  .module('fooModule', [])
  .factory('foo', function () {
    return 23;
  });

This example, which I already used above, is what AngularJS calls a value.

angular
  .module('fooModule', [])
  .value('foo', 23);

Now you might wonder what the difference is between this and the constant we used initially. Well, value actually registers a full-fledged provider, so there is a fooProvider whose $get method returns 23. Other providers can not have foo injected into them, only fooProvider. Constants are available also to provider functions directly.

This difference is a bit more than academic. AngularJS also has the concept of decorators, which allows modules to register functions that wrap around instances of providers of other modules. These functions are called with the result of $get, and return the value that is then actually stored. Another rarely-used feature. Constants can be intercepted like this.

Summary

AngularJS’ module system consists of two parts: Modules and dependency injection. Modules allow JavaScript files to be loaded in any order while the code itself is run in order of its dependencies. Dependency injection allows modules to be isolated in a way that makes it easy to test it.

When a module is retrieved, usually using the ng-app directive, this module and all modules in its dependency chain are ordered according to their dependencies.

In this order, AngularJS first executes all config blocks. These can register values with the injector for the following config blocks using $provide.constant or $provide.provider. As soon as $provide.provider is called, the argument function is called and the result stored as the fooProvider.

After all config blocks have been executed, the injector registers the actual values for providers. So if you registered a provider as foo, the provider itself is available as fooProvider. The first time foo is requested after the config blocks have executed, fooProvider.$get() is called, once, and its return value stored under foo for any further requests to that value.

And with that, you now understand all there is to AngularJS modules.