ankh

0.1.9 • Public • Published

ankh

Build Status

Fork me on GitHub

License

MIT

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.
 
 

Readme

Keywords

none

Package Sidebar

Install

npm i ankh

Weekly Downloads

10

Version

0.1.9

License

MIT

Last publish

Collaborators

  • mnichols