Di-Ninja
The Dependency-Injection Framework for JavaScript NodeJS and Browser.
Installation
$ npm i di-ninja
Goals
-
Implement IoC by Composition-Root design pattern, allowing to keep all things decoupled and to wire application components and config at one unique root place.
-
Replace the singleton anti-pattern with dependency-injection by refacto export of instances to export of classes and factories.
-
Get a pure JavaScript non-dogmatic cross-transpiller Dependency-Injection framework.
-
Encourage adherence to the best practices of Object Oriented design ( SOLID, IoC, Dependency-Injection, Composition-Root, Strategy …).
-
Improve code testability.
-
Extend the Art of JavaScript development.
Paradigm - Dependency Injection vs Modules
Why to not simply just use ES6 import or CommonJS require ?
Di-Ninja don’t pretend to replace these features, that would be useless.
Using import/export for getting class or factory function is the good way to use it, that’s why it was made for.
But using modules to export singleton is an anti-pattern
and leading to global state.
It predisposes application to more need for refacto in future, less testability (unable without hack).
How to know when to use dependency injection over import/require, it’s very simple:
when it’s about the implementation, always use dependency injection, even if it’s a factory that is exported,
else if it’s not an instance but a stateless input/output function with a general purpose,
usually coming from a third party library, you can use import. In case of doubt always use dependency injection !
Documentation
Summary
- Dependencies declarations approaches
- Dependencies Resolution
- Examples
1. Getting Started
import container from 'di-ninja'
const di = container()
di.addRules(rules)
di.get('MyClassName')
2. Dependencies declarations approaches
To define dependencies, you can use Composition-Root or Decorator injection approach for each components individually.
Differents approaches can be used for differents methods injections on same component.
Dependencies definition can be overrided, the last call of addRule or @di decorator will take precedence.
2.1 Composition-Root
The Composition Root is the highest level of your application, the top overlay.
It’s here that you will configure many rules for your components and wire them together.
Using only the Composition Root design pattern has the advantage to let your components totaly unopinionated,
all your classes and factories can keep uncoupled from the dependency injector (di-ninja).
example with ES6 class syntax
class A{
constructor(b){
this.b = b
}
}
class B{
constructor(){
this.foo = bar
}
}
di.addRules({
'A': {
classDef: A,
params: [ 'B' ],
},
'B': {
classDef: B,
},
}
})
di.get('A')
example with function as constructor or factory
//function as a constructor
function A(b){
this.b = b
}
//function as a factory
function B(){
const object = { foo: 'bar' }
// if we return an object or other value than undefined,
// this function will be treated by javascript as a factory
return object
}
di.addRules({
'A': {
classDef: A,
params: [ 'B' ],
},
'B': {
classDef: B,
},
})
di.get('A')
2.2 Decorator injection approach
The Decorator injection approach let your components define their own dependencies.
These dependencies declarations can rely on container level defined abstractions (recommanded),
or on direct class or factory definition.
It can be used in addition to the Composition-Root and replace the rule’s key params
and also the parameters of call argument for rule’s key calls
and lazyCalls
.
2.2.1 abstract class
example with ES6 class syntax
di.addRule('B',{
classDef: B,
})
@di('A',[ 'B' ])
class A{
constructor(b){
this.b = b
}
}
di.get('A')
example with function as constructor or factory
di.addRule('B',{
classDef: B,
})
function A(b){
this.b = b
}
di( 'A', ['B'] )( A )
di.get('A')
2.2.2 reference class
@di('A',[ B ])
class A{
constructor(b){
this.b = b
}
}
di.get('A')
3. Dependencies Resolution
The dependencies are resolved according to rule’s key params
and parameters of call argument for rule’s key calls
and lazyCalls
.
3.1 Recursive classes or factories
You can use factories or classes, and obviously, all dependencies are resolved recursively.
class A{
constructor(b){
this.b = b
}
}
class B{
constructor(c){
this.c = c
}
}
function C(){
return 'Hello world !'
}
di.addRules({
A: {
classDef: A,
params: [ 'B' ],
},
B: {
classDef: B,
params: [ 'C' ],
},
C: {
classDef: C,
},
})
const a = di.get('A') //will resolve C and pass it's return to new B, then it will pass the new B to new A
//it will be equivalent to
const a = new A( new B( C() ) )
3.2 Recursive params
You can nest dependencies declarations to infinite. It’s very common use for config.
(for others params behaviors see params
)
class A{
constructor(config, aSecondInstanceOfB){
this.b = config.wathever.anotherKey.b
this.b2 = aSecondInstanceOfB
}
}
class B{}
di.addRules({
A: {
classDef: A,
params: [ {
wathever: {
anotherKey: {
b: 'B'
},
},
}, 'B' ],
},
B: {
classDef: B,
},
})
const a = di.get('A')
//it will be equivalent to
const a = new A( {
wathever: {
anotherKey: {
b: new B(),
},
},
}, new B() )
3.3. Types of params
You can wrap each value of param with a di-ninja class that will tell container how to resolve the dependency.
By default all values and subvalues of params are traversed when it’s an Object or Array,
are wrapped with classFactory
when it’s a function, and else by interface
.
All these behaviors can be configured, but the default config is well and the documentation rely on it.
(see
defaultVar
,
defaultRuleVar
,
defaultDecoratorVar
,
defaultArgsVar
,
defaultFactory
,
defaultFunctionWrapper
)
(for others params behaviors see params
)
3.3.1 interface
Container will resolve dependency as, an instance of class or a value from factory, defined by corresponding rule’s key.
This is the default wrapper for string.
class A{
constructor(b){
this.b = b
}
}
class B{}
di.addRule('A', { classDef: A })
di.addRule('B', { classDef: B })
di.addRule('A', { params: [ di.interface('B') ] })
//with default config, previous rule will be equivalent to next one
di.addRule('A', { params: [ 'B' ] })
const a = di.get('A')
//will be equivalent to
const a = new A( new B() )
3.3.2 value
Container will resolve dependency with the specified value. The value type can be anything: scalar, object, array, function…
class A{
constructor(bar){
this.foo = bar
}
}
di.addRule('A', {
classDef: A,
params: [ di.value('bar') ],
})
const a = di.get('A')
//will be equivalent to
const a = new A( 'bar' )
3.3.3 factory
The behavior of this method can be configured with container config’s key defaultFactory
.
By default it’s an alias for valueFactory
.
3.3.4 valueFactory
Container will resolve dependency with the value returned by the given function.
class A{
constructor(bar){
this.foo = bar
}
}
function getFoo(){
return 'bar'
}
di.addRule('A', {
classDef: A,
params: [ di.factory( getFoo ) ],
})
const a = di.get('A')
//will be equivalent to
const a = new A( getFoo() )
3.3.5 classFactory
Container will resolve dependency with an instance of the referenced class (or the returned value of a factory).
This is the default wrapper for classes references.
class A{
constructor(b){
this.b = b
}
}
class B{}
di.addRule('A', { classDef: A })
di.addRule('A', { params: [ di.classFactory(B) ] })
//with default config, previous rule will be equivalent to next one
di.addRule('A', { params: [ B ] })
const a = di.get('A')
//will be equivalent to
const a = new A( new B() )
3.3.4 require
Container will resolve dependency with an instance (or value returned by the function)
of the class (or factory) (CJS export or ES6 export default) exported by specified file.
You can use rules to configure it.
The behavior of this method differ according to environment:
in all environment it will rely on preloaded require.context (see dependencies
)
wich is the only way to include dependency in webpack (because of static require resolution),
for node, if the dependency it’s not registred, it will require the specified file and register it.
di.addRules({
'A': {
classDef: A,
params: [ di.require('path/to/my-file') ],
},
'path/to/my-file': {
/* ... */
},
)
const a = di.get('A')
4. Rules
The rules define resolutions behaviors of the classes or factories and their dependencies.
const rules = {}
//1st way to define rules
const di = container()
di.addRules(rules)
//2nd way to define rules
const di = container({
rules,
})
4.1. dependencies
The following rule’s keys are about classes or factories dependencies.
//you can use class
class A{
constructor(b, c, d){
this.b = b
this.c = c
this.d = d
}
}
//or instance factory
function A(b, c, d){
this.b = b
this.c = c
this.d = d
}
//or factory
function A(b, c, d){
const anotherValue = {
b: b,
c: c,
d: d,
}
return anotherValue
}
4.1.1 params
type: array
containing nested dependencies
The rule’s key params
define what will be injected to class constructor or factory.
The keys can be nested (see Recursive params).
The resolutions behavior depends of Types of params.
class A{
constructor(b, c, d){
this.b = b
this.c = c
this.d = d
}
}
di.addRule('A', { params: ['B','C','D'] })
You can override params defined in rule on manual call:
di.get('A', ['E','F','G'])
4.1.2 calls
type: array
stack of call array with 1st item for method name or callback and 2nd item an array of params for methods (working same as params
).
Stack of methods to call after instance creation.
If some circular dependencies are detected, some items of calls stack will be placed automatically in lazyCalls
.
class A{
method1(dep1){
this.dep1 = dep1
}
method2(dep2){
this.dep2 = dep2
}
method3(dep3){
this.dep3 = dep3
}
}
di.addRule('A', {
classDef: A,
calls: [
[ 'method1', [ 'dep1' ] ],
[ 'method2', [ 'dep2' ] ],
[
function(a, dep3){
a.method3(dep3)
},
[ 'dep3' ]
],
],
})
4.1.3 lazyCalls
type: array
Same as calls
, but run after dependency has been distributed to needing instances, this helper offer a simple way to solving circular dependency problem.
4.2. instantiation
The following rule’s keys are about instantiations of classes and factories.
4.2.1 classDef
type: function class or factory
The classDef
key reference the class that will be used for instantiation.
It’s used for use reference to class direcly in rule, you can do without if you configure container dependencies
with require.context.
class A{}
di.addRule('A',{ classDef: A ])
assert( di.get('A') instanceof A )
4.2.2 instanceOf
type: string interface name
Refers to the name of another rule containing classDef
or instanceOf
, this is resolved recursively.
di.addRule('A',{ classDef: A })
di.addRule('B',{ instanceOf: 'A' })
assert( di.get('B') instanceof A )
4.2.3 substitutions
type: object | array
Substitutions, as indicated by it’s name,
substitutes a dependency defined by params
, calls
(and lazyCalls
).
If an object is provided, the substitutions operate by associative key, else, if an array is provided, the substitution will be done only for params
and will be index based.
By associative key, all dependencies of the rule’s named as the key will be replaced by specified other rule’s name.
index based
@di('A', [ 'B' ])
class A{
constructor(B){
this.B = B
}
}
di.addRule('A',{
substitutions: [ 'C' ],
})
assert( di.get('A').B instanceof C )
associative key
@di('A', [ { config: { subkey: 'B' } } ])
class A{
constructor(config){
this.B = config.subkey.B
}
}
di.addRule('A',{
substitutions: { 'B': 'C' },
})
assert( di.get('A').B instanceof C )
associative key for calls
class A{
setDep(config){
this.dep = config.dep
}
}
di.addRule('A',{
calls: [
[ 'setDep', [ { dep: 'B' } ] ],
],
substitutions: { 'B': 'C' },
})
assert( di.get('A').dep instanceof C )
4.3. single instance
The following rule’s keys are about sharing single instances.
4.3.1 shared
type: boolean (default false)
When shared
is set to true
, the instance of the classe or the factory return defined by the rule will be shared for the whole application.
class A{
constructor(b){
this.b = b
}
}
class B{}
di.addRules({
'A': {
params: [ 'B' ],
},
'B': {
shared: true,
},
})
const a1 = di.get('A')
const a2 = di.get('A')
assert( a1 !== a2 )
assert( a1.b === a2.b )
const b1 = di.get('B')
const b2 = di.get('B')
assert( b1 === b2 )
4.3.2 singleton
type: object | array | scalar
If specified it will be registred as shared instance of the dependency for the whole application.
class A{
constructor(b){
this.b = b
}
}
class B{}
const b = new B()
di.addRules({
'A': {
params: [ 'B' ],
},
'B': {
singleton: b,
},
})
const a1 = di.get('A')
const a2 = di.get('A')
assert( a1 !== a2 )
assert( a1.b === a2.b === b )
const b1 = di.get('B')
const b2 = di.get('B')
assert( b1 === b2 === b )
4.3.3 sharedInTree
In some cases, you may want to share a a single instance of a class between every class in one tree but if another instance of the top level class is created, have a second instance of the tree.
For instance, imagine a MVC triad where the model needs to be shared between the controller and view, but if another instance of the controller and view are created, they need a new instance of their model shared between them.
The best way to explain this is a practical demonstration:
class A {
constructor(b, c){
this.b = b
this.c = c
}
}
class B {
constructor(d){
this.d = d
}
}
class C {
constructor(d){
this.d = d
}
}
class D {}
di.addRule('A', {
'sharedInTree': ['D'],
})
const a = di.get('A')
// Anywhere that asks for an instance D within the tree that existis within A will be given the same instance:
// Both the B and C objects within the tree will share an instance of D
assert( a.b.d === a.c.d )
// However, create another instance of A and everything in this tree will get its own instance of D:
const a2 = di.get('A')
assert( a2.b.d === a2.c.d )
assert( a.b.d !== a2.b.d )
assert( a.c.d !== a2.c.d )
By using sharedInTree
it’s possible to mark D as shared within each instance of an object tree.
The important distinction between this and global shared objects is that this object is only shared within a single instance of the object tree.
4.4. rule inheritance
The following rule’s keys are about rule inheritance.
There are three way to herit rule, the priority order of override is
inheritInstanceOf
,
overrided by inheritPrototype
,
overrided by inheritMixins
,
and finally the rule itself wich is composed by rule definition and decorator
.
Priority between rule and decorator depends of calls order, the last take precedence, traditionally it’s the rule.
Most of rules options are replaced except some options that will be merged:
sharedInTree
, substitutions
,
calls
and lazyCalls
.
See also rulesDefault
in container config section.
4.4.1 inheritInstanceOf
type: boolean (default true)
Enable inheritance of rules from instanceOf parents classes.
class X{
constructor(x){
this.x = x
}
}
di.addRules({
'X':{
classDef: X,
params: [ di.value('ok') ],
shared: true,
},
'Y':{
instanceOf: 'X',
inheritInstanceOf: true,
},
})
assert( di.get('Y').x === 'ok' )
assert( di.get('Y') === di.get('Y') )
4.4.2 inheritPrototype
type: boolean (default false)
Enable inheritance of rules from ES6 extended parents classes.
decorator
must be enabled to parents rules you want to extend from.
class Z{
constructor(...params){
this.params = params
}
}
class ZX extends Z{}
di.addRules({
'Z': {
classDef: Z,
params: [ di.value(1), di.value(2), di.value(3) ],
decorator: true, //needed for parent class by extended using inheritPrototype
},
'Z2': {
classDef: ZX,
inheritPrototype: true,
},
})
const z = di.get('Z').getParams()
const z2 = di.get('Z2').getParams()
assert.deepEqual(z2, z)
4.4.3 inheritMixins
type: array
Enable inheritance from a list of specified rules.
class A{
constructor(...params){
this.params = params
}
getParams(){
return this.params
}
}
class B{
constructor(...params){
this.params = params
}
getParams(){
return this.params
}
}
di.addRules({
'A':{
classDef: A,
params: [ di.value('a') ],
},
'B':{
classDef: B,
inheritMixins: [ 'A' ],
},
})
const a = di.get('A').getParams()
const b = di.get('B').getParams()
assert.deepEqual(b, a)
4.4.4 decorator
type: boolean (default false)
When set to true
, a Symbol
property
will be set on class or factory function, allowing to use inheritPrototype
.
If the decorator injection approach is used, it’s not necessary to configure this rule,
because the Symbol will be set whatever the decorator key value is.
This is required to enable inheritPrototype
feature.
4.5. asynchronous dependencies resolution
The following rule’s keys allow you to manage the asynchronous dependencies resolution flow.
When a dependency return a Promise
and this promise is waited for resolution by asyncResolve
,
the outputed object of di.get()
method will be a Promise object,
wich will be resolved by the expected object.
4.5.1 asyncResolve
type: boolean (default false)
When set to true
, if a factory return a Promise, the dependency tree will wait for it’s resolution,
and then call the requiring dependency with the Promise’s resolved value.
Promise is detected with instanceof operator, if you want to use a specific Promise polyfill (eg: bluebird) you can use
the promiseFactory
and promiseInterfaces
container’s config options.
function A(b, c){
this.b = b
this.c = c
}
async function B(){
return 'b'
}
async function C(){
return 'c'
}
di.addRules({
'A': {
classDef: A,
params: ['B','C'],
},
'B': {
classDef: B,
asyncResolve: true,
},
'C': {
classDef: C,
asyncResolve: false, //default
},
})
di.get('A').then(a => {
assert(a.b === 'b')
assert(a.c instanceof Promise)
})
4.5.2 asyncCallsSerie
type: boolean (default false)
When set to true
, defer calls
resolution sequentially
when the method or callback require a dependency returning a Promise and for wich asyncResolve
rule option setted to true.
class A{
setB(d){
this.b = ++d.i
}
setC(d){
this.c = ++d.i
}
}
function B(d){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(d)
}, 200)
})
}
function C(d){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(d)
}, 100)
})
}
function D(){
this.i = 0
}
di.addRules({
'A': {
classDef: A,
calls: [
['setB', ['B'] ],
['setC', ['C'] ],
],
sharedInTree: ['D'],
asyncCallsSerie: false, //default
},
'A2': {
instanceOf: 'A',
asyncCallsSerie: true,
},
'B': {
classDef: B,
params: ['D'],
asyncResolve: true,
},
'C': {
classDef: C,
params: ['D'],
asyncResolve: true,
},
'D':{
classDef: D,
},
})
di.get('A').then( a => {
assert.strictEqual(a.b, 2)
assert.strictEqual(a.c, 1)
} )
di.get('A2').then( a => {
assert.strictEqual(a.b, 1)
assert.strictEqual(a.c, 2)
} )
4.5.3 asyncCallsParamsSerie
type: boolean (default false)
When set to true
, ensure that the dependencies stacks for all calls
of a dependency are resolved sequentially according to order of calls,
when the method or callback require a dependency returning a Promise and for wich asyncResolve
rule option setted to true.
Setted to true, it will implicitly set asyncCallsSerie
to true.
class A{
setB(b){
this.b = b
}
setC(c){
this.c = c
}
}
function B(d){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(++d.i)
}, 200)
})
}
function C(d){
return new Promise((resolve)=>{
setTimeout(()=>{
resolve(++d.i)
}, 100)
})
}
function D(){
this.i = 0
}
di.addRules({
'A': {
classDef: A,
calls: [
['setB', ['B'] ],
['setC', ['C'] ],
],
asyncCallsParamsSerie: true,
sharedInTree: ['D'],
},
'B': {
classDef: B,
params: ['D'],
asyncResolve: true,
},
'C': {
classDef: C,
params: ['D'],
asyncResolve: true,
},
'D':{
classDef: D,
},
})
di.get('A').then( a => {
assert(a.b === 1)
assert(a.c === 2)
})
4.6 dependency file location
The following rule’s keys are about dependency file location.
4.6.1 autoload
type: boolean (default false)
When set to true
, check for allready registred dependency and if not, in node, try to require it,
if dependency is not found it can (maybe) throw an Error according to autoloadFailOnMissingFile
container config.
The require path resolution is based first on path
rule option if defined,
then on instanceOf
rule option if defined (if instanceOf point to a rule with it’s own path it will get it),
and finally on the key of the rule.
This require path can be post-processed using autoloadPathResolver
container config.
The colons character :
can be used to get a subkey of exported,
and you can use it multiple times in same expression to get nested value.
When path
is defined it will implicitly set autoload to true.
di.addRules({
'http:Server':{
autoload: true,
},
'#server': {
instanceOf: 'http:Server',
autoload: true,
},
'#server2': {
path: 'http:Server',
},
})
assert( di.get('http:Server') instanceof require('http').Server )
assert( di.get('#server') instanceof require('http').Server )
assert( di.get('#server2') instanceof require('http').Server )
4.6.2 path
type: string
The require path can be post-processed by autoloadPathResolver
container config.
When defined it will implicitly set autoload
to true.
You can traverse exported and get specific key using colons character :
.
You can’t use relative path, if you want to include relative path, your application source files for exemple,
you have to alias directories (or files) using autoloadPathResolver
feature.
See autoload
section for more details on the requiring behavior based on implicit path with instanceOf and rule’s key.
di.addRules({
'#server': {
path: 'http:Server',
},
})
assert( di.get('#server') instanceof require('http').Server )
5. Container
The container config options manage the container behavior and the way that the rules are resolving.
import container from 'di-ninja'
//set config on container creation
const di = container(config)
//or using config method
const di = container()
di.config(config)
di.config('aConfigKey', aConfigValue)
Order of config calls doesn’t matter except for options dependencies
and rules
wich must be called at end.
If key/value config object is provided as config param, options will be treated in the correct order.
5.1 rules
See rules
section.
di.config({
rules
})
//or
di.config('rules',rules)
//or
di.addRules(rules)
di.addRule('#myClassName', rule)
5.2 rulesDefault
Default values for rules, each rule will be extended from this setup, values will be overidded or merged.
See rule inheritance documentation section for more details on extending.
See rules
section for rules options.
di.config('rulesDefault',rulesDefault)
5.3 dependencies
Dependencies is intendeed to allow you to “inject” require’s context directories as preload dependencies.
It work using the webpack require.context
feature,
but a node polyfill called container.context
is provided with di-ninja allowing you to build isomorphic architecture.
see also polyfillRequireContext
NodeJS example
import container from 'di-ninja'
const di = container({
rules:{
'app/A': {
},
'app/B': {
},
'app/B/C': {
},
},
dependencies: {
'app' : container.context('./src', true, /\.js$/),
'A': container.require('./src/A'),
'B': container.dependency(require('./src/B')),
},
})
assert( di.get('app/A') instanceof require('./src/A').default )
assert( di.get('app/B') instanceof require('./src/B').default )
assert( di.get('app/B/C') instanceof require('./src/B/C').default )
assert( di.get('A') instanceof require('./src/A').default )
assert( di.get('B') instanceof require('./src/B').default )
Isomorphic example
Use the same code for browser compilation (via webpack for example) than on server-side with nodejs.
in webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.DefinePlugin({
'process.env': {
APP_ENV: JSON.stringify('browser')
}
}),
],
/* ... */
}
and in your dependency-injection config file
import container from 'di-ninja'
if(process.env.APP_ENV !== 'browser'){
//we are not in webpack/browser but in nodejs/server-side
require.context = container.context
}
const di = container({
rules:{
'app/A': {
},
'app/B': {
},
'app/B/C': {
},
},
dependencies: {
'app' : require.context('./src', true, /\.js$/),
'A': container.dependency( require('./src/A') ),
'B': container.dependency( require('./src/B') ),
},
})
assert( di.get('app/A') instanceof require('./src/A').default )
assert( di.get('app/B') instanceof require('./src/B').default )
assert( di.get('app/B/C') instanceof require('./src/B/C').default )
assert( di.get('A') instanceof require('./src/A').default )
assert( di.get('B') instanceof require('./src/B').default )
5.4 autoloadPathResolver
Rule’s path post-processor callback function or alias map object.
with callback
di.config('autoloadPathResolver', (path)=>{
switch(path){
case 'framework':
path = 'express'
break
}
return path
})
with alias map object
import path from 'path'
const aliasMap = {
'app': path.resolve(__dirname, './src'),
}
di.config('autoloadPathResolver', aliasMap)
//will implicitly set the following predefined callback, here is the magic under the hood
di.config('autoloadPathResolver', (path)=>{
Object.keys(aliasMap).forEach(alias=>{
const realPath = aliasMap[alias]
if(path == alias){
path = realPath
}
else if(path.substr(0,alias.length+1)==alias+'/'){
path = realPath+path.substr(alias.length)
}
})
return path
})
5.5 autoloadExtensions
You can use Array or RegExp.
di.config('autoloadExtensions', ['js', 'jsx'])
di.config('autoloadExtensions', new RegExp('\.(js|jsx)$'))
5.6 autoloadFailOnMissingFile
Possible values are string path
, true
or false
.
Setted to false, it will never throw error on missing dependency.
Setted to true, it will always throw an error on missing dependency.
Setted to path
, it will throw error only if a dependency with a specified rule’s option path
is specified.
The default value is path
.
di.config('autoloadFailOnMissingFile', true)
5.7 defaultVar
Value by default for
defaultRuleVar
,
defaultDecoratorVar
,
and defaultArgsVar
.
This is about implicit wrapping of params
, calls
and lazyCalls
.
Possible values are interface
or value
.
Default is interface
.
interface
mean that all scalar values will be implicitly wrapped by di.interface(), it will be resolved as class.
value
mean that all scalar values will be wrapped by di.value(), it will be left as untouched.
interface example
di.config('defaultVar', 'interface') //default
di.addRule('A', {
params: [
{
B : 'B',
message : di.value( 'Hello world !' ),
config : di.value({
option1: value1
/* ... */
}),
}
]
})
value example
di.config('defaultVar', 'value')
di.addRule('A', {
params: [
{
B : di.interface('B'),
message : 'Hello world !',
config : di.value({
option1: value1
/* ... */
}),
}
]
})
5.8 defaultRuleVar
Implicit wrapping for scalar values defined from rules
.
see defaultVar
.
5.9 defaultDecoratorVar
Implicit wrapping for scalar values defined from decorator
.
see defaultVar
.
5.10 defaultArgsVar
Implicit wrapping for scalar values defined from manual call (see params
).
see defaultVar
.
5.11 defaultFactory
default wrapper class to instanciate for di.factory() method.
5.12 defaultFunctionWrapper
Implicit wrapping for function or class values.
Possible values are ClassFactory or ValueFactory.
The default value is ClassFactory.
ClassFactory example
import ClassFactory from 'di-ninja/dist/classFactory'
di.config('defaultFunctionWrapper', ClassFactory) //default
class B{}
di.addRule('A', {
params: [
B,
di.valueFactory( () => {
return 'Hello world !'
} )
],
})
ValueFactory example
import ValueFactory from 'di-ninja/dist/valueFactory'
di.config('defaultFunctionWrapper', ValueFactory)
class B{}
di.addRule('A', {
params: [
di.classFactory( B ),
() => {
return 'Hello world !'
}
],
})
5.13 promiseFactory
default: Promise (global)
promiseFactory
option let you modify the way DiNinja create Promise for handle asyncResolve
.
For example, you can use it with bluebird.
The common way is to use it in combination with promiseInterfaces
option.
import bluebird from 'bluebird'
di.config('promiseFactory', bluebird)
function A (b) {
this.b = b
}
function B () {
return new Promise((resolve, reject) => {
resolve('b')
})
}
di.addRules({
'A': {
classDef: A,
params: ['B']
},
'B': {
classDef: B,
asyncResolve: true
}
})
assert( di.get('A') instanceof bluebird )
5.14 promiseInterfaces
default: Promise (global)
promiseInterfaces
option let you modify the way DiNinja recognize Promise.
For example, you can use it with bluebird.
The common way is to use it in combination with promiseFactory
option.
The promiseFactory
option will automatically be pushed to promiseInterfaces.
import bluebird from 'bluebird'
di.config('promiseInterfaces', [ bluebird, Promise /* let the standard Promise be identified */ ])
function A(b){
this.b = b
}
function B(){
return new bluebird((resolve, reject)=>{
resolve('b')
})
}
di.addRules({
'A': {
classDef: A,
params: ['B'],
},
'B': {
classDef: B,
asyncResolve: true,
},
})
assert( di.get('A') instanceof Promise )
5.15 interfacePrototype
Enable you to use Symbol
based
interface reference instead of string as dependency key, allowing you to use runtime interfaceTypeCheck
.
import {
InterfacePrototype,
instanceOf,
Interface,
} from 'interface-prototype'
di.config('interfacePrototype', InterfacePrototype)
const I = new Interface();
@di('A')
@instanceOf(I)
class A{}
@di('B')
@instanceOf(I)
class B{}
di.addRules({
[I]: {
classDef: A,
}
})
@di('D', [I])
class D {
constructor(i){
this.i = i;
}
}
assert(di.get('A') instanceof I)
assert(di.get('B') instanceof I)
assert(di.get(I) instanceof A)
assert( !( di.get(I) instanceof B ))
assert(di.get('D').i instanceof A)
5.16 interfaceTypeCheck
type: boolean (default false)
Enable check for “implemented” interface using interfacePrototype
.
If a manually provided dependency doesn’t “implement” the required “interface”, it will throw an error.
In combination with interface-prototype,
this enable runtime type-check and custom type-check for all type of variables.
5.17 globalKey
type: string | boolean (default false)
When setted to true it will be transformed to string ‘di’.
If provided, global.globalKey (on node) or window.globalKey (on browser) will be set to the instance of container.
container({
globalKey: 'di',
})
di.get('A')
5.18 ruleCache
type: boolean (default true)
Enable cache for built rules, optimizing the future calls of di.get()
.
You can set it to false when you have rules dynamically modified after some di.get()
calls.
di.config('ruleCache', false)
di.addRule('A', { params: [ 'B' ] })
const a1 = di.get('A')
di.addRule('A', { params: [ 'C' ] })
const a2 = di.get('A')
5.19 polyfillRequireContext
type: boolean (default false)
This is an experimental feature, so be carefull.
It will automatically polyfill the webpack’s require.context
method to nodejs environment.
It’s an helper to enforce easy isomorphism (cross-environment).
See also dependencies
for the hack-less technic
6 Examples
Here is a list of examples of complex di-ninja implementations with dependencies wired by directories:
- di-ninja-nodejs-example (using di-ninja node polyfill for
require.context()
) - di-ninja-webpack-example (using
require.context()
native method of webpack) - di-ninja-reactnative-example (using babel-plugin-require-context-polyfill)
About
Built with babel.
Work with NodeJS, Webpack, React-Native and UMD/AMD (requirejs).
Inspired by strategy for php, itself based on dice design.
Made with ❤️ and 🦊.