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.