radish
Dependency injection container.
Last updated 6 years ago by bluejeansandrain .
MIT · Repository · Bugs · Original npm · Tarball · package.json
$ cnpm install radish 
SYNC missed versions from official npm registry.

radish

JavaScript dependency injection container.

Install

npm install radish --save

Quick Start Example

var Radish = require('radish');

var container = new Radish();

container.service('main', function($container, $baz)
{
	console.log($baz);
	console.log($container.get('baz'));
	console.log($container.get('baz'));
});

container.service('foo', 'foo');
container.service('bar', function($foo)
{
	return $foo + 'bar';
});

var count = 0;
container.factory('baz', function($bar)
{
	return $bar + (count++);
});

container.get('main');

Output:

foobar0
foobar1
foobar2

API

All functions that are registered as services, factories, or providers, will automatically be passed services when they are called, based on their parameter list.

You can also create a stand alone function that will be passed service values the same way by wrapping it using the .inject() method. Any arguments that the wrapped function is called with will still be passed to the function around the injected service values.

Service request parameter names are detecting by being prefixed with a dollar $ sign character, or by having a comment that begins with a dollar sign.

Examples:

container.service('bar', 'Hello world!')
container.service('foo', function($bar, /* $bar */ baz)
{
	// $bar will be 'Hello world!' because that's the value of the bar service.
	// baz will also be 'Hello world!' because it has a comment which indicates
	// that it is requesting the bar service.
});

var callback = container.inject(function(arg0, $bar, arg1)
{
	console.log(arg0 + $bar + arg1);
});

callback("Whoa!!! ", " What's shakin'?");
// Whoa!!! Hello world! What's shakin'?

new Radish([Radish|Function ...providers])

The constructor can optionally take one or more providers. This is the same as passing each argument to the .provider() method after the container has been constructed.

All containers have an automatically defined "container" service that returns the container itself.

.service(String name, any service) -> this

Register a service value or a service initializer function.

If the service argument is a function, then it will be called the first time the service is requested. Whatever the function returns will become the service value and will be returned whenever the service is requested without re-calling the function. This allows for late/on-demand creation of the service value in case the value is not immediately available or may not be needed.

Any service argument that is not a function will become the service value immediately, and will be returned whenever the service is requested.

.method(string name, Function method) -> this

What if you want a service that returns a function as its value instead of calling the function?

You can use the .service() method as described above to register a function that returns a function, like this:

container.service('foo', function($container)
{
	return $container.inject(function($bar, $baz, some, additional, args)
	{

	});
});

Or, you can use this method as a shortcut:

container.method('foo', function($bar, $baz, some, additional, args)
{

});

Either way you can now request the foo service and be returned a function which will automatically have any dependencies injected.

var foo = container.get('foo');
foo('some', 'additional', 'args');

.factory(string name, Function factory) -> this

Register a "getter" function that will be called every time the service is requested. Whatever the function returns each time it's called will be the service value for that particular service request.

This can be useful in cases where the service should return a new instance of some class each time it's requested.

.provider(Radish|Function provider) -> this

A provider can be another Radish container instance, or a function that will register multiple related services.

If a container instance is given, then all the services registered on that container will be available through this container. The services on the provider container are not copied, but instead a reference to the other container is kept and this container will fall back to the provider container if a service is requested that that it doesn't know about directly.

If a function is given, it will be called immediately. The value of this will be the container. Like all container methods that accept functions, services will be injected based on the parameter names.

container.provider(function($container)
{
	$container.service('foo', function()
	{

	});

	$container.factory('bar', function($foo)
	{

	});
});

This makes more sense if you have the provider defined in its own module.

Module: foo_bar_provider

module.exports = function($container)
{
	$container.service('foo', function()
	{

	});

	$container.factory('bar', function($foo)
	{

	});
};

Main Module

container.provider(require('foo_bar_provider'));

.get(string|Function what) -> any

Rather than using the dollar $ sign prefix notation for registered or injected function parameters, you can request a service directly from a container using this method.

// This...
container.service('foo', function($bar)
{

});

// ... is equivalent to this.
container.service('foo', function($container)
{
	var $bar = $container.get('bar');
});

You can also pass a function to inject and immediately call that function. In this case, .get() will return the container instead of a service value. The value of this in the callback will also be the container.

container.get(function($foo, $bar)
{
	// I get called immediately, $foo and $bar will be resolved to the "foo"
	// service value.
});

### `.has(string name) -> boolean`

If a service is requested that does not exist, no Error will be thrown. The service request will just return undefined.
As such, it's not possible to determine if a service exists but is returning undefined, or if it is returning undefined
because it does not exist. This method will return true only if the service has been defined by a call to the
`.service()`, `.factory()`, or `.provider()` methods, regardless of what the service value is.

This method also _does not_ invoke the service or factory function like `.get()` would.

### `.inject(Function callback) -> Function`

Wrap a function so that dependencies are automatically injected each time it's called, based on the names of it's
parameters.

```js
var foo = container.inject(function($bar, arg0)
{
	// When foo is called, $bar will be the injected "bar" service as defined on
	// the container, and arg0 will be the first argument that was passed to foo
	// when called.
});

.protect(any callback) -> any

If the callback parameter is a function, then wrap it in a function that returns the original function. If callback is not a function, then just return it as is.

This is mostly for use in concert with the .service() method, which assumes any function value it gets is an initializer that should be called to get the actual value of the service. For external modules that aren't aware of the dependency injection framework, this can be a little clunky.

For instance, this might trip you up.

container.service('lodash', require('lodash'));

Strangely enough, the lodash module returns a function even though that's not readily apparent from their documentation. The above example results in a TypeError when you try to get the lodash service. To be on the safe side, if you are translating third party modules into services, use the .protect() method to ensure that whatever the require returns becomes the service value.

container.service('lodash', container.protect(require('lodash')));

Module System Integration

Node modules are already a form of dependency injection, so why would you want to use a dependency injection container? Mostly for making testable and maintable code at the function level instead of at the file level. Consider the following example.

Module: bar

var foo = require('foo');

function bar()
{
	foo();
}

module.exports = bar;

Main Module

var bar = require('bar');

bar();

That's a pretty common Node module structure. How would you test bar independantly of foo though? You would need to somehow hack or replace Node's module system so that require('foo') returned a mock. Now compare this to the following alternative which uses Radish.

Module: bar

function bar($foo)
{
	$foo();
}

module.exports = bar;

Main Module

var Radish = require('radish');

var container = new Radish();

// You do have to do a little more setup in main to register the your modules as
// a services.
container.service('foo', function()
{
	return require('foo');
});

// By registering bar as a service or a factory, we can have the container call
// it with any dependencies automatically injected, just by requesting it with
// the container `.get()` method. That will work as long as all of bar's
// arguments should be injected services, because there's no way to pass
// additional arguments to a service initializer or factory function.
container.factory('bar', require('bar'));

// Call the bar function with the foo service automatically injected.
container.get('bar');

Your main module just got a little more complicated, because you have to register your required modules as services, but this can be an advantage for more than one reason.

First, the bar module exported function now accepts foo as an argument. This makes it much easier to test because you can require the bar module which doesn't have any requires of its own, and you can pass foo in as anything you want for testing purposes.

Second, you can use IoC to make modules that behave differently depending on how dependencies are defined. For instance, if the foo module were actually a data access interface, you could have your main module redefine foo depending on what data store you want to use to back your application. The bar module no longer has to know if it's using Oracle or MongoDB, it works against foo which can be implemented to work with either. You could of course use the second definition of bar which accepts foo as an argument without using a DI container. The container just provides a structured way of managing dependencies.

Third, since you've moved most or all of your require() calls into a single file, that file serves as a sort of glossary of used modules which can make your code much easier to understand. It will even still work with browserify as long as you always pass string literals to require() calls!

Current Tags

  • 1.2.0                                ...           latest (6 years ago)

8 Versions

  • 1.2.0                                ...           6 years ago
  • 1.1.1                                ...           6 years ago
  • 1.1.0                                ...           6 years ago
  • 1.0.2                                ...           6 years ago
  • 1.0.1                                ...           6 years ago
  • 1.0.0                                ...           6 years ago
  • 0.1.0                                ...           6 years ago
  • 0.0.1                                ...           6 years ago
Maintainers (1)
Downloads
Today 0
This Week 0
This Month 6
Last Day 0
Last Week 4
Last Month 3
Dependencies (2)
Dev Dependencies (2)
Dependents (1)

Copyright 2014 - 2016 © taobao.org |