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.