ankh
v0.1.9
Published
Dependency Injection for JS
Downloads
13
Readme
ankh
License
Dependency Injection for JavaScript
Overview
I missed an IoC container in JavaScript that let me control the lifestyle and lifecycle of components. I couldn't find a library that gave me such granular control, so I rolled my own.
Install
npm install --save ankh
Glossary
- activator the method to use for construction of an component
- component the 'thing' to resolve at runtime, regardless of whether it was constructed by
ankh
or not - lifecycle the activation and resolution flow of construction a component
- lifestyle the duration of a components life in the container.
Components & lifestyle
Components may be constructed by ankh
, or not. If they are then they will be managed
by ankh
as configured by their 'lifestyle'. Components created by ankh
are, by default,
transient
; meaning you get a new instance for each resolution. ankh
can
guarantee a singleton
instance when you register using { lifestyle: 'singleton'}
.
Components which are managed outside the container, such as instance
or value
registrations,
will ignore any lifestyle directive.
API
Registering components
Register a singleton factory
var Ankh = require('ankh')
var ankh = new Ankh()
//register a singleton factory
HandlerFactory.inject = ['http']
function HandlerFactory(http) {
return function handle(command) {
return http(...)
}
}
ankh.factory('handlerFactory',HandlerFactory, {
lifestyle: 'singleton'
})
Register a transient (default) prototype
//register a prototype
Issue.inject = ['cfg']
function Issue(cfg) {
this.cfg = cfg
}
Issue.prototype.doIt = function(){}
ankh.ctor('issue',Issue, {
lifestyle: 'transient'
})
Register a value
//note that this clones the value, so changes to the object are not reflected
//in the container
var cfg = { root: '/api'}
ankh.value('cfg',cfg)
Register an instance
ankh.instance('bluebird',require('bluebird'))
Register an decorator
//first register your services
// with '@impl' as a special key for the instance you want to decorate
MyDecoratingService.inject = ['@impl']
function MyDecoratingService(impl){
//do things to the instance
//return whatever you want
}
ankh.factory('myTargetService',function(){})
ankh.factory('myDecoratingService',MyDecoratingService)
//register the decoration
ankh.decorate('myTargetService','myDecoratingService')
```
### Resolving components
#### Manual resolution
Typically, you won't manually resolve components, but of course during testing this
is useful. `ankh` will accept a second parameter object matching the key to the dependency to
override resolution. This is also great for testing for pushing mocks and the like
into your instances.
```js
MyService.inject = ['dep1','dep2']
function MyService(dep1,dep2) {
}
//register
ankh.value('dep1','FOO')
ankh.value('dep2','BAR')
ankh.ctor('svc',MyService)
//resolve
ankh.resolve('svc',{ dep2: 'BAZ'}) // -> use 'BAZ' for dep2 value
.then(function(it) {
//do things with 'svc'
})
ankh.resolve('svc') // -> use 'BAR' for dep2 value
.then(function(it) {
//do things with svc
})
```
#### Lifestyle : Start and startables (bootstrap)
`ankh` exposes a `start` method that can act as a bootstrapper for an application.
Components may participate in this lifecycle event by attaching an `startable` property
on the entity being registered. `ankh` will construct the instance (and resolve any
dependencies) and immediately invoke the method name provided by `startable`.
```js
Settings.startable = 'load'
function Settings() {
return {
load: function() {
//do stuff
}
}
}
ankh.factory('settings',Settings)
ankh.start().then(...)
```
Sometimes you don't want to resolve the same dependencies during this period for a component,
so you may provide an `inject` array for the method in the same fashion as you
configure your component's dependencies.
```js
Settings.startable = 'load'
function Settings() {
var spec = {
load: function(cache) {
//do stuff with cache
}
}
spec.load.inject = ['localStorage']
return spec
}
ankh.factory('settings',Settings)
ankh.start().then(...)
```
#### Lifestyle : Initializable
When `ankh` resolves a dependency it will invoke the method name provided by the
special `initializable` property on the registered entity. This happens _each time_
the component is resolved.
```js
User.initializable = 'fetchUser'
function User(xhr) {
var spec = {
userName: undefined
, fetchUser: function(xhr) {
return xhr('profile')
.bind(this)
.tap(function(usr) {
this.userName = usr.name
})
}
}
spec.inject = ['xhr']
return spec
}
Settings.inject = ['user']
function Settings(user) {
return {
sayHi: function() {
console.log('hi',user.userName)
}
}
}
```
#### Deferrables
Sometimes a component needs to defer construction of a dependency. This can come up when:
* Multiple instances of the same dependency are desired, but may not be registered with unique names
* Some action should take place before the dependency is meaningful (rare)
* A dynamic configuration should be used to override a dependency of the dependency (rarer)
In all these cases, I prefer being explicit about such needs and register that behavior as
a component itself. However sometimes that isn't warranted or possible.
To that end, `ankh` exposes a `deferrable` method that accepts the `key` of the service
you want to make deferrable and an (optional) key to name the resulting component. If you don't
provide a name, it will use `{serviceName}Deferred` by convention. Behind the scenes
`ankh` is merely putting the service behind a function closure and forwarding arguments
to defer it's construction on-demand.
```js
Settings.inject = ['localStorage']
function Settings(cache) {
}
ankh.factory('settings',Settings)
ankh.deferrable('settings')
Login.inject = ['settingsDeferred'] //the special sauce
function Login(settingsDeferred) {
return {
localCache: {}
, signin: function() {
//settingsDeferred is a function
//you can even provide your own overrides
return settingsDeferred({ cache: this.localCache })
.then(function(settings) {
//do stuff with new instance
})
}
}
}
```
### Validating the container for acyclic dependencies (experimental)
`ankh` tries hard to tell you if the container has cyclic dependencies.
Decorators _can_ be difficult to detect this.
```js
try {
var graph = ankh.validate() // if OK then an array of execution order is returned
} catch(err) {
//otherwise an error is throw trying to identify failure
}
```
## Docs
Run `make view-docs` to see pretty documentation
## Example
There is an example of creating and using the `ankh` container in the `/examples` folder.
## Tests
`ankh` uses [karma](http://karma-runner.github.io/0.12/index.html).
You can `make test` to runem.
#### Thanks and Inspirations
The work here is heavily influenced from the IoC concepts found in Castle Project's
[Windsor Inversion of Control Container](https://github.com/castleproject/Windsor).
The success of [Angular](https://angularjs.org/) demonstrated for me that this is one of those patterns that
belongs in dynamic language world just as much as it does in static lang.