immutable-di

1.3.35 • Public • Published

immutable-di Build Status

NPM

Simple, dependency injection container with some state handling functions.

General

  • Install: npm install --save immutable-di immutable-di-react
  • Tests: npm test
  • Examples: npm run dev.examples

Why not *-flux?

Our main focus make Flux-like API as less and simple as possible. Which with less words you can express more. The ideas behind similar to the redux, baobab, nuclear-js, but implementation based on dependency injection. And of course you can use dependency injection as standalone.

Usecases

React all-in example

import {default as React, Component} from 'react';
import {Getter, Facet, Factory, Setter} from 'immutable-di/define'
import root from 'immutable-di-react/root'
import statefull from 'immutable-di-react/statefull'
import Container from 'immutable-di'
import NativeCursor from 'immutable-di/cursors/native'
 
const cursor = new NativeCursor({
  tis: {
    a: 1,
    b: 2
  }
})
 
const container = new Container(cursor)
 
var abbaFacet = Facet({
  a: ['tis', 'a']
})(function bloomyFacet({a}) {
  return a + 10;
})
 
 
var ChangeAction = Factory({
  setA: Setter(['tis', 'a']),
  setIsLoading: Setter(['tis', 'isLoading'])
})(function ({setA, setIsLoading}) {
  return function (num) {
    // Here will be all mutate state logic. for example server side request
    // communication with API layer and etc.
    setIsLoading(true);
    $.get('/server/route').then((data) => {
      setIsLoading(false);
      var a = num + data.a;
      setA(a);
    })
 
  }
});
 
@root()
@statefull({
    abba: abbaFacet,
    changeAction: ChangeAction
})
class Application extends React.Component {
  handleClick () {
    this.props.changeAction(100);
  }
  render () {
    return <div onClick={this.handleClick.bind(this)}>Bloom: {this.props.abba}</div>
  }
}
 
 
export default function () {
  React.render(<Application container={container} />, document.querySelector('.app'));
}

Define dependency

import {Facet, Factory, Class} from 'immutable-di/define'
// A, B - functions or classes with di definitions
 
// For functions:
Factory([A, B])(C) // resolve functions A, B and pass them as arguments to C
Factory({a: A, b: B})(C) // resolve functions A, B and pass them as object {a, b} to C
 
//Facet - same as Factory, but do not cache factory return value
 
// For classes:
@Class([A, B])
class C {
    constructor(a, b) {
 
    }
}
 
// or
class C {
    constructor(a, b) {
 
    }
}
 
export default Class([A, B])(C)
 
// or
@Class({
    a: A,
    b: B
})
class C {
    constructor({a, b}) {
    }
}
 
// for State
@Class([
    A,
    B,
    options: ['config', 'c']
])
class C {
    constructor(a, b, options) {
 
    }
}

Working with state

import Container from 'immutable-di'
import NativeCursor from 'immutable-di/cursors/native'
 
const cursor = new NativeCursor({
    config: {
        logger: {
            opt1: 'test1'
        },
        mod2: {
            opt1: 'test2'
        }
    }
})
 
// define di container with state:
const container = new Container(cursor)
 
// dep 1:
function MyFaset(state) {
    return 'data'
}
Factory()(MyFaset)
 
// dep 2:
function myHandler({state, faset}) {
    console.log(state, faset)
}
 
// bind listener:
const listener = container.on({
    state: ['config', 'logger'],
    faset: MyFaset
}, myHandler)
 
// trigger my hander
cursor.select(['config', 'logger', 'opt1']).set('test')
 
// path config.logger not affected, myHandler is not triggered
cursor.select(['config', 'mod2', 'opt1']).set('1')
 
// unbind listener:
container.off(listener)

Di factory example

import Container from 'immutable-di'
import {Factory, Class} from 'immutable-di/define'
import NativeCursor from 'immutable-di/cursors/native'
 
const cursor = new NativeCursor({
    config: {
        logger: {
            opt1: 'test1'
        }
    }
})
const container = new Container(cursor)
 
function ConsoleOutputDriver() {
    return function consoleOutputDriver(str) {
        console.log(str)
    }
}
Factory()(ConsoleOutputDriver)
 
@Class([
    ConsoleOutputDriver,
    ['config', 'logger']
])
class Logger {
    constructor(outputDriver, dep, config) {
        this._outputDriver = outputDriver
        this._config = config
    }
    log(val) {
        this._outputDriver('val:' + val + ', opt:' + this._config.opt1)
    }
}
 
function SomeDep() {
    return 'dep'
}
Factory()(SomeDep)
 
function App({logger, someDep}) {
    return function app(val) {
        logger.log(val + someDep)
    }
}
Factory({
    logger: logger,
    someDep: SomeDep
})(App)
 
container.get(App)('test') // outputs: val: testdep, opt: test1

Cache example

import Container from 'immutable-di'
import {Factory, Class} from 'immutable-di/define'
import NativeCursor from 'immutable-di/cursors/native'
 
const cursor = new NativeCursor({
    config: {
        myModule: {
            opt1: 'test1'
        }
    }
})
const container = new Container(cursor)
 
function MyModule({opt1}) {
    console.log('init', opt1)
 
    return function myModule(val) {
        console.log('out', opt1, ', val', val)
    }
}
Factory([
    ['config', 'myModule']
])(MyModule)
 
container.get(MyModule) // outputs init test1
container.get(MyModule) // no outputs, return from cache
const cursor = cursor.select(['config', 'myModule', 'opt1'])
 
cursor.set('test2') // outputs test2
container.get(MyModule) // no outputs: return from cache
 
container.get(MyModule)('test3') // outputs out test2, val test3

React example

// my-faset.js
function myFaset(todos) {
    return todos.map(todo => todo.id + '-mapped')
}
 
export default Factory([
    ['todoApp', 'todos']
])(myFaset)
// todo-list.js
import statefull from 'immutable-di-react/statefull'
import root from 'immutable-di-react/root'
import TodoListItem from './todo-list-item'
import myFaset from './my-faset'
import TodoActions from './todo-actions'
 
// set container from props to context:
@root()
// bind to setState:
@statefull({
    todos: ['todoApp', 'todos'], // state path
    query: ['todoApp', 'query'], // state path
    mapped: myFaset, // faset
    actions: TodoActions // class with actions
})
export default class TodoList extends React.Component {
    render({todos, mapped, actions}) {
        return (
            <div>
                <div>
                    <h3>Mapped todo ids:</h3>
                    {mapped.toString()}
                </div>
                <ul>
                    {todos.map(todo => (
                        <TodoListItem todo={todo}/>
                    ))}
                </ul>
            </div>
        )
 
    }
}
// todo-list-item.js
import React from 'react'
import widget from 'immutable-di-react/widget'
import di from 'immutable-di-react/di'
import TodoActions from './todo-actions'
 
function TodoListItem({todo, editMode, actions}) {
    return (
        <li className='todos-list-item'>
            {todo.title}
        </li>
    )
}
 
export default Di({
    actions: TodoActions
})(widget(TodoListItem))
// todo-actions.js
import Container from 'immutable-di'
import {Class} from 'immutable-di/define'
 
@Class([Container])
export default class TodoActions {
    constructor(container) {
        this._cursor = cursor.select(['todoApp'])
    }
 
    addTodo(todo) {
        this._cursor.apply(['todos'] => todos.concat(todo))
    }
}
// index.js
import React from 'react'
import Container from 'immutable-di'
import NativeCursor from 'immutable-di/cursors/native'
import TodoList from './todo-list'
 
// define di container with state:
const cursor = new NativeCursor({
    todoApp: {
        todos: [],
        query: {
 
        }
    }
})
const container = new Container(cursor)
 
const initialProps = cursor.select(['todoApp']).get()
React.render(<TodoList ...initialProps container={container}/>, document.querySelector('body'))

Initial debug support

import {Factory, Setter} from 'immutable-di/define'
import Container from 'immutable-di'
import NativeCursor from 'immutable-di/cursors/native'
import MonitorFactory from 'immutable-di/history/MonitorFactory'
 
Factory.extend = MonitorFactory
 
function showChanges(history) {
    console.log(history)
}
 
const action = Factory([
    Setter(['tis', 'a'])
])(function MyAction(setA) {
    return function myAction(value) {
        setA(value)
    }
})
 
const cursor = new NativeCursor({
    tis: {
        a: 1,
        b: 2
    }
})
const container = new Container(cursor)
const listener = container.on([
    ['__history']
], showChanges)
 
container.get(action)(123)
// Will produce:
/*
[
    { "displayName": "MyAction", "id": 11, "args": [ 123 ], "diff": {} }
]
*/
container.off(listener)

NativeCursor:diff used for diff generation, this dummy, but NativeCursor can be extended.

Package Sidebar

Install

npm i immutable-di

Weekly Downloads

88

Version

1.3.35

License

MIT

Last publish

Collaborators

  • zerkalica