Experiment: Create Dependency Injection Like Angular Js

JavaScript has a lot of limitations, but that doesnt mean people doesn't adopt design patterns commonly found in other languages. One of the patterns I want to mention is Dependency Injection (DI).

Angular Js has the following styles to define dependencies (and register services):

  • By Supplying Dependencies Name in Array

    angular.factory('serviceName', ['dependency1','dependency2', function(dep1, dep2){
    ....
    });
    

    The above style is the common one as it is the most flexible.

  • By Infer Dependencies By Arguments Name Directly

    angular.factory('serviceName', function(dependency1, dependency2){
    ...
    });
    

Note that I use angular.factory here as an example, this annotation also works in other angular context like module, controller, etc.

Those two approaches for DI are certainly not as neat as what I usually use in .NET, given lack of JavaScript capabilities.

Today I want to try to make a simple example to demonstrate dependency injection in JavaScript.
Please note that I didn't check Angular source code. I'm sure it is more complex and is more suited to its own purpose.

Let's start by using this scenario

var container =new Container();

container.register('dependency1', function(){
	return function(){
		console.log('my name is dependency 1');
	}
});

container.register('dependency2', ['dependency1', function(dependency1){
	return  {
		doSomething: function(){
			dependency1();
			console.log('do something');
		}
	}
}]);

container.register('testService', function(dependency1, dependency2){
	return function(val){
		dependency1();
		dependency2.doSomething();
		console.log(val);
	};
});

container.resolve('testService')(12);

So we have a dependency1 which is registered by function arguments inferrence, and dependency2 which registered with array parameters. Function testService is the one that we will resolve.

The first step is we need to create Container class.

function Container(){
	this.services = []; //hold list of services
}

For array based registration, we know that the function itself is the last parameter of array, and the others are dependency names.

Container.prototype.registerByArray = function(name, args){
	var registration = {
		name: name,
		func: args[args.length-1],
		dependencies: args.splice(0,args.length-1)
	};
	this.services.push(registration);
};

For function arguments based registration, JavaScript can't get method parameters' names like .NET's reflection API. The only way to get argument names is via toString() and we need to parse it manually :(

Container.prototype.registerByArgs = function(name, func){
	var strFunc = func.toString();
	var params =strFunc.substring(strFunc.indexOf('(')+1, strFunc.indexOf(')')).split(',');
	var dependencies = [];
	for(var i=0; i < params.length; i++){
		var param = params[i].trim();
		if(param == '')
			continue;
		dependencies.push(param);
	}
	this.services.push({
		name: name,
		func: func,
		dependencies: dependencies
	});
}

In this example, we just use transient (instance per dependency) resolve mechanism because that one is the simplest and the most common one.

Container.prototype.resolve= function(name){
	var registration = this._getServiceByName(name);
	var deps = [];
	for(var i=0; i < registration.dependencies.length; i++){
		deps.push(this.resolve(registration.dependencies[i]));
	}
	return registration.func.apply(null, deps);
}

Tweaking the above code to support singleton lifetime is relatively trivial.
The code can be viewed here