Angular JS and Dependency Injection

Angular JS and Dependency Injection work together to create a powerful extensible framework

Written by 42ID on October 04 2014

Dependency Injection, as you know, is a software pattern that allows loosely coupled discrete software modules to get hold of and use other modules that they need. It allows for the dependent modules to be “injected” into modules without the requesting module having to worry about locating, creating or maintaining the lifecycle of the dependent modules.

In Angular, dependency injection happens at almost every step. It’s one of the reasons that makes Angular so flexible.

Before we see how Dependency Injection (DI) works in Angular, let’s take a look at why we would need them in our application. Assume that you have core business logic that might be used by different controllers (and what application doesn’t). Where would you put this logic? Assume you have logic that performs data access logic – maybe reaches out to some REST API and returns data. And this logic will be used by other controllers in other parts of the application. Where would you put this logic?

Keeping with the DRY (Don’t Repeat Yourself) principle, you want to place this logic in one place, but have it such that your controllers can easily access this.

Consider another scenario. Assume you are building a library that you are planning on distributing to the community. Your module will be used by controllers not written by you. Ideally you want the other controllers to use your module without having to worry about instantiating and maintaining the lifecycle of the objects in your library.

While we are at it, let’s consider yet another scenario. Let’s say you want to swap out your data access logic to use mock data for testing. And you want to do this in a minimally invasive method. It must be as simple as “switching” out your real modules with modules that provide mock data. What pattern can we use to make this easy for us?

If the above scenarios lead you to architect a solution in your mind along the lines of Dependency Injection, then you are on the right path. Any application that you write is going to present you with plethora of opportunities to use DI. And using DI in appropriate places in your code, not only makes for good software development practice, but it helps in readability and maintainability of your code.

Using DI in Angular

Now that we can identify situations where we would use DI, how do we go about doing this in Angular?

The provider method on an Angular modules allows us to create injectable components. This method works in concert with the $injector object which instantiates the injectable component and makes it available to the modules that request it.

The provider method takes in 2 parameters – the name of the provider and a constructor function that the $injector uses to instantiate the injectable component. Internally, Angular takes the service name and adds the suffix ‘Provider’ to the name.(If you ever need to reference this provider, you will have to use this full name that includes the Provider suffix.)

app.provider(dataAccess, function(){
	// constructor function
});

The constructor function exposes a $get method that returns the implementation of the injectable component.

app.provider(dataAccess, function(){
	// constructor function
	// some initialization methods, properties etc,
	$get : function(){
		//  functionality exposed by this injectable object.
		// this is the functionality that is available to the components that use this injectable
		// component
		return {
			names: [‘AngularJs, ‘Javascript’]
		}
	}
});

When this injectable component is requested, the $injector checks its cache for previously instantiated instance of this component and if none is found, uses the constructor method in the service provider to create a new instance and returns this to the requesting component.

Other components make use of this service by passing in the name of the service as parameters into their constructor function.

app.controller(controllerName, function(dataAccess) {
	var x = dataAccess.names;
});

In many cases, the services that are created, don’t require any special initialization properties or methods. In these cases, Angular provides a convenience method called factory to create these services. The factory method internally calls the provider method to create the service constructor.

app.factory(‘dataAccess’, function(){
	return{
names: [‘AngularJs, ‘Javascript’]
	}
});

Minification

In Angular, DI is achieved by resolving the name of the parameters against the cache of available service definitions maintained by the $injector object. When the javascript file is minfied, the parameter names will be changed by the minification process and subsequently the DI engine in Angular will not be able to resolve the service names. In order to protect against this, Angular provides an inline array method of specifying the dependencies.

app.controller(controllerName, [‘dataAccess’, function(da) {
	var x = da.names;
}]);

Using this method, the dependencies are specified as string literals in an array, with the last element of the array being the function definition that uses the dependencies. The dependencies are resolved by Angular by looking up the string literal in the array and the corresponding function parameter is associated with the injected component.

Provider vs Factory

When would you use the provider method over the convenience factory method?

In order to understand this, we need to look into the lifecycle of Angular application. When an Angular application is instantiated, it goes through 2 phases – the config and the run phase. During the config phase, the directives, controllers and filters get set up. During the run phase, Angular compiles your DOM and runs your applications – this is where you start interacting with the DOM and all the 2-way binding magic happens. Angular allows us to run additional code during the config and run phase. For example, to add code to be executed during the config and run phase, you would do :

app.config(function(){
	// run me during config phase
});
app.run(function(){
	// run me during the run phase
});

But during the config stage, services are NOT available for injection. However, the service providers are available during this phase. And during the run phase, the services are available for injection, but service providers are NOT available. This is an important distinction.

So, if we have a service that we need to configure or change some setting on it prior to it being used in the run phase, we would create it using the provider method so that it gives us access to modify its behavior during the config phase.

app.provider(‘log’, function(){
   var logLevel = 0;
   var setLogLevel(level){
	logLevel = level;
   }
   
   $get: function(){
      return function (message) (
	If (logLevel == 0){
		// don’t log anything
		return;
	}
	If(logLevel == 1){
		// log messages here
		console.log(message);
	} 
      }
    }
});

app.config(function(logProvider){
	// run me during config phase
	logProvider.setLogLevel(1);
});
app.run(function(log){
	// run me during the run phase
	log(‘application started’);
});

Was this useful?

Have questions? We have answers. Contact us, we are glad to help! Spread the word if you found this useful! Tweet us on @thelatestbrian and @yanamegainfo