While DI is widely used in statically typed languages such as Java or C++, it has not been widely used in JavaScript. Angular brings the benefits of DI into JavaScript apps.
In angular, DI is implemented as a subsystem that manages dependencies between services, controllers, widgets, and filters.
Services are objects that handle common tasks in web applications. Angular provides several built-in services
, and you can create your
own custom services.
The main job of angular's DI subsystem is to provide services to angular components that depend on
them. The way the DI subsystem provides services is as follows: all services are registered with
angular's service API
, and all components that depend on services
define those dependencies as a property ($inject
). With this information, the DI subsystem
manages the creation of service objects and the provision of those objects to the components that
need them, at the time they need them. The following illustration steps through the sequence of
events:
In the illustration above, the dependency injection sequence proceeds as follows:
ng-app
triggers bootstrap sequence on given element, during which angular creates injector,
loads "phonecat" and "ng" modules and compiles the template.ng-controller
directive implicitly creates a new child scope and instantiates
PhoneListCtrl
controller.$http
service as PhoneListCtrl
controller's only dependency.$http
service has already been instantiated.
If not uses the provider from the available modules to construct it.$http
service to the PhoneListCtrl
controller constructor.The root scope of the application is just a service that is available for injection to any part of the application under the service name "$rootScope".
EXPERIMENTAL FEATURE: This is an experimental feature. See the important note at the end of this section for drawbacks.
We resort to $inject
and our own annotation because there is no way in JavaScript to get a list
of arguments. Or is there? It turns out that calling .toString()
on a function returns the
function declaration along with the argument names as shown below:
function myFn(a,b){} expect(myFn.toString()).toEqual('function myFn(a,b){}');
This means that angular can infer the function names after all and use that information to generate
the $inject
annotation automatically. Therefore the following two function definitions are
equivalent:
// given a user defined service angular.module('module1', [], function($provide) { $provide.factory('serviceA', ...); }); // inject '$window', 'serviceA', curry 'name'; function fnA($window, serviceA, name){}; fnA.$inject = ['$window', 'serviceA']; // inject '$window', 'serviceA', curry 'name'; function fnB($window, serviceA_, name){}; // implies: fnB.$inject = ['$window', 'serviceA'];
If angular does not find a $inject
annotation on the function, then it calls the .toString()
method and tries to infer what should be injected by using function argument names as dependency
identifiers.
IMPORTANT
Minifiers/obfuscators change the names of function arguments and will therefore break the $inject
inference. For this reason, either explicitly declare the $inject
or do not use
minifiers/obfuscators. In the future, we may provide a pre-processor which will scan the source
code and insert the $inject
into the source code so that it can be minified/obfuscated.