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 = var ankh = //register a singleton factory HandlerFactoryinject = 'http' { return { return }} ankh
Register a transient (default) prototype
//register a prototype Issueinject = 'cfg' { thiscfg = cfg }Issueprototype{} ankh
Register a value
//note that this clones the value, so changes to the object are not reflected//in the containervar cfg = root: '/api'ankhvalue'cfg'cfg
Register an instance
ankh
Register an decorator
//first register your services// with '@impl' as a special key for the instance you want to decorateMyDecoratingServiceinject = '@impl'{ //do things to the instance //return whatever you want}ankhankh //register the decorationankh``` ### Resolving components #### Manual resolution Typically, you won't manually resolve components, but of course during testing thisis useful. `ankh` will accept a second parameter object matching the key to the dependency tooverride resolution. This is also great for testing for pushing mocks and the likeinto your instances. ```js MyServiceinject = 'dep1''dep2' { } //registerankhvalue'dep1''FOO'ankhvalue'dep2''BAR'ankh //resolveankh // -> use 'BAZ' for dep2 value ankh // -> use 'BAR' for dep2 value ``` #### 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` propertyon the entity being registered. `ankh` will construct the instance (and resolve anydependencies) and immediately invoke the method name provided by `startable`. ```js Settingsstartable = 'load' { return { //do stuff } }ankh ankhstart ``` 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 youconfigure your component's dependencies. ```js Settingsstartable = 'load' { var spec = { //do stuff with cache } specloadinject = 'localStorage' return spec} ankh ankhstart ``` #### Lifestyle : Initializable When `ankh` resolves a dependency it will invoke the method name provided by thespecial `initializable` property on the registered entity. This happens _each time_the component is resolved. ```js Userinitializable = 'fetchUser' { var spec = userName: undefined { return } specinject = 'xhr' return spec} Settingsinject = 'user' { return { console } } ``` #### 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 serviceyou want to make deferrable and an (optional) key to name the resulting component. If you don't provide a name, it will use `serviceNameDeferred` by convention. Behind the scenes`ankh` is merely putting the service behind a function closure and forwarding argumentsto defer it's construction on-demand. ```js Settingsinject = 'localStorage' { }ankhankh Logininject = 'settingsDeferred' //the special sauce { return localCache: {} { //settingsDeferred is a function //you can even provide your own overrides return } } ``` ### 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 // if OK then an array of execution order is returned catcherr //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 thatbelongs in dynamic language world just as much as it does in static lang.