Dependency Injection in Angular JS

Dependency Injection (DI) is a software design pattern that deals with how components get hold of their dependencies.

The Angular injector subsystem is in charge of creating components, resolving their dependencies, and providing them to other components as requested.

For in-depth discussion about DI, see Dependency Injection at Wikipedia, Inversion of Control by Martin Fowler, or read about DI in your favorite software design pattern book

DI in a Nutshell

There are only three ways a component (object or function) can get a hold of its dependencies:

  1. The component can create the dependency, typically using the new operator.
  2. The component can look up the dependency, by referring to a global variable.
  3. The component can have the dependency passed to it where it is needed.

The first two options of creating or looking up dependencies are not optimal because they hard code the dependency to the component. This makes it difficult, if not impossible, to modify the dependencies. This is especially problematic in tests, where it is often desirable to provide mock dependencies for test isolation.

The third option is the most viable, since it removes the responsibility of locating the dependency from the component. The dependency is simply handed to the component.

  1. function SomeClass(greeter) {
  2. this.greeter = greeter;
  3. }
  4. SomeClass.prototype.doSomething = function(name) {
  5. this.greeter.greet(name);
  6. }

In the above example SomeClass is not concerned with creating or locating the greeter dependency, it is simply handed the greeterwhen it is instantiated.

This is desirable, but it puts the responsibility of getting hold of the dependency on the code that constructs SomeClass.

To manage the responsibility of dependency creation, each Angular application has an injector. The injector is a service locator that is responsible for construction and lookup of dependencies.

Here is an example of using the injector service:

  1. // Provide the wiring information in a module
  2. var myModule = angular.module(‘myModule’, []);

Teach the injector how to build a greeter service. Notice that greeter is dependent on the $window service. The greeter service is an object that contains a greet method.

  1. myModule.factory(‘greeter’, function($window) {
  2. return {
  3. greet: function(text) {
  4. $window.alert(text);
  5. }
  6. };
  7. });

Create a new injector that can provide components defined in our myModule module and request our greeter service from the injector. (This is usually done automatically by angular bootstrap).

  1. var injector = angular.injector([‘myModule’, ‘ng’]);
  2. var greeter = injector.get(‘greeter’);

Asking for dependencies solves the issue of hard coding, but it also means that the injector needs to be passed throughout the application. Passing the injector breaks the Law of Demeter. To remedy this, we use a declarative notation in our HTML templates, to hand the responsibility of creating components over to the injector, as in this example:

  1. <div ng-controller=”MyController”>
  2. <button ng-click=”sayHello()”>Hello</button>
  3. </div>
  1. function MyController($scope, greeter) {
  2. $scope.sayHello = function() {
  3. greeter.greet(‘Hello World’);
  4. };
  5. }

When Angular compiles the HTML, it processes the ng-controller directive, which in turn asks the injector to create an instance of the controller and its dependencies.

  1. injector.instantiate(MyController);

This is all done behinds the scenes. Notice that by having the ng-controller ask the injector to instantiate the class, it can satisfy all of the dependencies of MyController without the controller ever knowing about the injector.

This is the best outcome. The application code simply declares the dependencies it needs, without having to deal with the injector. This setup does not break the Law of Demeter.

Dependency Annotation

How does the injector know what components need to be injected?

The application developer needs to provide annotation information that the injector uses in order to resolve the dependencies. Throughout Angular, certain API functions are invoked using the injector, as per the API documentation. The injector needs to know what services to inject into the function. There are three equivalent ways of annotating your code with service name information:

  • Implicitly from the function parameter names
  • Using the $inject property annotation
  • Using the inline array annotation

These can be used interchangeably as you see fit and are equivalent.

Implicit Dependencies

The simplest way to get hold of the dependencies, is to assume that the function parameter names are the names of the dependencies.

  1. function MyController($scope, greeter) {
  2. // …
  3. }

Given a function the injector can infer the names of the service to inject by examining the function declaration and extracting the parameter names. In the above example $scope, and greeter are two services which need to be injected into the function.

While straightforward, this method will not work with JavaScript minifiers/obfuscators as they rename the method parameter names. This makes this way of annotating only useful for pretotyping, and demo applications.

$inject Property Annotation

To allow the minifers to rename the function parameters and still be able to inject right services, the function needs to be annotated with the $inject property. The $inject property is an array of service names to inject.

  1. var MyController = function(renamed$scope, renamedGreeter) {
  2. }
  3. MyController[‘$inject’] = [‘$scope’, ‘greeter’];

In this scenario the ordering of the values in the $inject array must match the ordering of the arguments to inject. Using above code snippet as an example, $scope will be injected into renamed$scope and greeter into renamedGreeter. Care must be taken that the$inject annotation is kept in sync with the actual arguments in the function declaration.

This method of annotation is useful for controller declarations since it assigns the annotation information with the function.

Inline Array Annotation

Sometimes using the $inject annotation style is not convenient such as when annotating directives or services defined inline by a factory function.

For example:

  1. someModule.factory(‘greeter’, function($window) {
  2. // …
  3. });

Results in code bloat due to needing a temporary variable:

  1. var greeterFactory = function(renamed$window) {
  2. // …
  3. };
  4. greeterFactory.$inject = [‘$window’];
  5. someModule.factory(‘greeter’, greeterFactory);

For this reason the third annotation style is provided as well.

  1. someModule.factory(‘greeter’, [‘$window’, function(renamed$window) {
  2. // …
  3. }]);

Here, instead of simply providing the factory function, we pass an array, whose elements consist of a list of strings (the names of the depenendencies) followed by the function itself.

Keep in mind that all of the annotation styles are equivalent and can be used anywhere in Angular where injection is supported.

Where Can I Use DI?

DI is pervasive throughout Angular. You can use it when defining components or when providing run and config blocks for a module.

  • Components such as services, directives, filters and animations are defined by an injectable factory method or constructor function. These components can be injected with “service” components as dependencies.
  • The run and config methods accept a function, which can also be injected with “service” components as dependencies.
  • Controllers are defined by an constructor function, which can be injected with any of the “service” components as dependencies, but they can also be provided with special dependencies. See “DI in Controllers” below.

Factory Methods

Factory methods are responsible for creating most objects in Angular. Examples are directives, services, and filters. The factory methods are registered with the module, and the recommended way of declaring factories is:

  1. angular.module(‘myModule’, [])
  2. .factory(‘serviceId’, [‘depService’, function(depService) {
  3. }])
  4. .directive(‘directiveName’, [‘depService’, function(depService) {
  5. }])
  6. .filter(‘filterName’, [‘depService’, function(depService) {
  7. }]);

Module Methods

We can specify functions to run at configuration and run time for a module by calling the run and config methods. These functions are injectable with dependencies just like the factory functions above.

  1. angular.module(‘myModule’, [])
  2. .config([‘depProvider’, function(depProvider){
  3. }])
  4. .run([‘depService’, function(depService) {
  5. }]);

Controllers

Controllers are “classes” or “constructor functions” that are responsible for providing the application behavior that supports the declarative markup in the template. The recommended way of declaring Controllers is using the array notation:

  1. someModule.controller(‘MyController’, [‘$scope’, ‘dep1’, ‘dep2’, function($scope, dep1, dep2) {
  2. $scope.aMethod = function() {
  3. }
  4. }]);

This avoids the creation of global functions for controllers and also protects against minification.

Controllers are special in that, unlike services, there can be many instances of them in the application. For example, there would be one instance of a for every instance of the ng-controller directive in the template.

Moreover, additional dependencies are made available to Controllers:

  • $scope: Controllers are always associated with a point in the DOM and so are provided with access to the scope at that point. Other components, such as servies only have access to the singleton $rootScope service.
  • $route resolves: If a controller is instantiated as part of a route, then any values that are resolved as part of the route are made available for injection into the controller.