Developer Guide: Dependency Injection

Work in Progress This page is currently being revised. It might be incomplete or contain inaccuracies.

Dependency injection (DI) is one of the core design patterns in angular and angular applications. DI allows you to replace almost any part of angular framework or angular application with a custom implementation, allowing for a highly flexible, maintainable and testable code-base.

Dependency injection is a very common pattern in Java and other statically typed languages. While undervalued among JavaScript developers, we feel strongly that DI in JavaScript allows us to achieve the same benefits as in other languages.

This document will focus on using dependency injection in angular. It is outside of the scope of this document to explain details of dependency injection. For more information on this topic, please refer to these links:

Dependency Injection in angular

Angular's dependency injection story begins with a service. Service in angular lingo is a JavaScript object, function, or value that is created by angular's injector via a provided factory function. The factory function is registered with angular via angular.service.

// register a factory for a uniqueId service.
angular.service('uniqueId', function(){
  // calling the factory function creates the instance function
  var id = 0;
  return function(){
   // calling the counter instance function will return and increment the count
   return ++id;
  }
});

At run-time we can access the uniqueId service by looking it up with the service locator like this:

// create new root scope which has the injector function `$service()`
var scope = angular.scope();

// use the `$service` function to look up the service instance function
var idGenerator = scope.$service('uniqueId');
expect(idGenerator()).toBe(1);

// subsequent lookups using the same root scope return the service instance
var idGenerator2 = scope.$service('uniqueId');
expect(idGenerator).toBe(idGenerator2);

// since it is same instance calling idGenerator2 returns 2;
expect(idGenerator2()).toBe(2);

The service registry seems like a lot of work, so what are the benefits? To answer this question, it’s important to realize that in large scale applications there are a lot of services which are often dependent on each other, as in this example:

angular.service('gadgetFactory', function(uniqueId){
  return function(){
    return {gadgetId: uniqueId()};
  };
}, {$inject: ['uniqueId']});

Specifically, notice that the gadgetFactory takes uniqueId service in its arguments. It also declares this dependency with the $inject property. There are several benefits to this approach:

More importantly, as we'll soon learn, controllers and other components of angular applications can also declare their dependencies on services and these will be provided without explicitly looking them up, but let's not get ahead of ourselves.

Lastly, it is important to realize that all angular services are singletons – application singletons to be more precise. This means that there is only one instance of a given service per injector. And since angular is lethally allergic to the global state, it's absolutely possible to create multiple injectors each with its own instance of a given service (but that is not typically needed, except in tests where this property is crucially important).

Service Locator and Scope

The injector is responsible for resolving the service dependencies in the application. It gets created and configured with the creation of a root scope in your application. The injector is responsible for caching the instances of services, but this cache is bound to the scope. This means that different root scopes will have different instances of the injector. While typical angular applications will only have one root scope (and hence the services will act like application singletons), in tests it is important to not share singletons across test invocations for isolation reasons. We get this isolation by having each test create its own separate root scope.

// crate a root scope
var rootScope = angular.scope();
// accesss the service loctor
var myService = rootScope.$service('myService');

Dependency Injection in Controllers

So far we have been talking about injector as a service locator. This is because we have been explicitly calling the $service method to gain access to the service. Service locator is not dependency injection since the caller is still responsible for retrieving the dependencies. True dependency injection is like Chuck Norris. Chuck does not ask for dependencies; he declares them.

The most common place to use dependency injection in angular applications is in controllers. Here’s a simple example:

function MyController($route){
  // configure the route service
  $route.when(...);
}
MyController.$inject = ['$route'];

In this example, the MyController constructor function takes one argument, the (@link angular.service.$route $route) service. Angular is then responsible for supplying the instance of $route to the controller when the constructor is instantiated. There are two ways to cause controller instantiation – by configuring routes with the $route service or by referencing the controller from the HTML template, such as:

<!doctype html>
<html xmlns:ng="http://angularjs.org" ng:controller="MyController">
 <script src="http://code.angularjs.org/angular.min.js" ng:autobind></script>
 <body>
  ...
 </body>
</html>

When angular is instantiating your controller, it needs to know what services, if any, should be injected (passed in as arguments) into the controller. Since there is no reflection in JavaScript, we have to supply this information to angular in the form of an additional property on the controller constructor function called $inject. Think of it as annotations for JavaScript.

MyController.$inject = ['$route'];

The information in $inject is then used by the injector to call the function with the correct arguments.

Using Dependency Injection pragmatically

At times you’ll need to use dependency injection pragmatically, usually when instantiating controllers manually or writing unit tests. This section explains how to go about it.

Retrieving Services

The simplest form of dependency injection is manual retrieval of scopes, known as service locator. We say manual because we are asking the injector for an instance of the service (rather then having the injector provide them to the function). This should be rare since most of the time the dependent services should be injected into the controller using the $inject property array.

// create a root scope. The root scope will automatically have
// `$service` method defined which is configured with all services.
// Each instance of root scope will have separate instances of services.
var rootScope = angular.scope();

// ask for a service explicitly
var $window = rootScope.$service('$window');

Creating Controllers using Dependency Injection

In a typical angular application the dependency injection is most commonly used when creating controllers.

// declare our own service by registering a factory function.
angular.service('counter', function(){
  var count = 0;
  return function(){ return count++; };
});

// example of a controller which depends on '$window' and 'counter' service
// notice that there is an extra unbound parameter 'name' which will not
// be injected and must be supplied by the caller.
function MyController($window, counter, name) {
}

// we must declare the dependencies explicitly and in the same order as in
// the constructor function. This information is used by the dependency
// injection to supply the arguments.
// Notice the lack of 'name' argument which makes it an unbound argument.
MyController.$inject = ['$window', 'counter'];


// Create a root scope which creates the the injector
var rootScope = angular.scope();

// use the '$new()' method instead of standard 'new' keyword operator to
// create an instance of MyController and have the dependency injection
// supply the arguments to the controller. The dependency injection only
// supplies the bound arguments in `$inject` all addition arguments are
// curried from the '$new', in our case 'Alexandria' is the argument which
// will be curried to the 'name' argument, while '$window' and 'counter'
// are supplied by the dependency injection.
var myController = rootScope.$new(MyController, 'Alexandria');
// NOTE: the returning controller will be a child scope of parent scope,
// in this case the root scope.

Calling functions and Curring of arguments

NOTE: this section is quite lame. The concept it is trying to describe is more closely related to scope#new than scope#$service. We need a better example to discuss here. Ideally a parent controller creating a child controller imperatively via $new where the child controller's constructor function declares a portion of its dependencies via $inject property, but another portion is supplied by the caller of $new (e.g. parentCtrl.$new(ChildCtrl, configParam1, configParam2);

Finally, you may need to call functions but have the $inject properties of the function be supplied by the injector.

// create a root scope with the `$service` injector.
var rootScope = angular.scope();

// given a function such as
function greet ($window, name) {
  $window.alert(this.salutation + ' ' + name);
}
greet.$inject = ['$window'];

// you can call function 'greet' such that the injector supplies the
// '$window' and the caller supplies the function 'this' and the 'name'
// argument.
var fnThis = {salutation: 'Hello'}
rootScope.$service(greet, fnThis, 'world');

Inferring $inject

* EXPERIMENTAL: 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.service('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: fnA.$inject = ['$window', 'serviceA'];

If angular does not find an $inject annotation on the function, then it calls the .toString() and tries to infer what should be injected using the following rules:

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.