Own JS/CSS options

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.

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.

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.

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.)

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.

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.

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.

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.