├── index.js ├── src ├── index.js ├── singleton.js ├── inject.js └── container.js ├── .gitignore ├── .npmignore ├── lib ├── singleton.js ├── index.js ├── inject.js └── container.js ├── LICENSE ├── package.json ├── test └── index.js └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default as container} from './container'; 2 | export {singleton} from './singleton'; 3 | export {inject} from './inject'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_* 2 | node_modules 3 | *.idea/* 4 | *.iml 5 | dest 6 | out 7 | .DS_Store 8 | *.log 9 | *.pid 10 | *.seed 11 | *.ipr 12 | *.iws -------------------------------------------------------------------------------- /src/singleton.js: -------------------------------------------------------------------------------- 1 | import container from './container'; 2 | 3 | export function singleton(Clazz) { 4 | container.registerAsSingleton(Clazz); 5 | } 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_* 2 | node_modules 3 | *.idea/* 4 | *.iml 5 | dest 6 | test 7 | out 8 | .DS_Store 9 | *.log 10 | *.pid 11 | *.seed 12 | *.ipr 13 | *.iws -------------------------------------------------------------------------------- /src/inject.js: -------------------------------------------------------------------------------- 1 | import container from './container'; 2 | 3 | export function inject(Interface) { 4 | return (target,key,desc)=> { 5 | if(typeof target != 'function'){ 6 | desc.initializer = ()=>{ 7 | return container.getInstanceOf(Interface); 8 | } 9 | }else{ 10 | container.registerDependencies(target,...arguments); 11 | } 12 | 13 | }; 14 | } -------------------------------------------------------------------------------- /lib/singleton.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | exports.singleton = singleton; 7 | 8 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 9 | 10 | var _container = require('./container'); 11 | 12 | var _container2 = _interopRequireDefault(_container); 13 | 14 | function singleton(Clazz) { 15 | _container2['default'].registerAsSingleton(Clazz); 16 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequire(obj) { return obj && obj.__esModule ? obj['default'] : obj; } 8 | 9 | var _container = require('./container'); 10 | 11 | exports.container = _interopRequire(_container); 12 | 13 | var _singleton = require('./singleton'); 14 | 15 | Object.defineProperty(exports, 'singleton', { 16 | enumerable: true, 17 | get: function get() { 18 | return _singleton.singleton; 19 | } 20 | }); 21 | 22 | var _inject = require('./inject'); 23 | 24 | Object.defineProperty(exports, 'inject', { 25 | enumerable: true, 26 | get: function get() { 27 | return _inject.inject; 28 | } 29 | }); -------------------------------------------------------------------------------- /lib/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | var _slice = Array.prototype.slice; 7 | exports.inject = inject; 8 | 9 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 10 | 11 | var _container = require('./container'); 12 | 13 | var _container2 = _interopRequireDefault(_container); 14 | 15 | function inject(Interface) { 16 | var _arguments = arguments; 17 | 18 | return function (target, key, desc) { 19 | if (typeof target != 'function') { 20 | desc.initializer = function () { 21 | return _container2['default'].getInstanceOf(Interface); 22 | }; 23 | } else { 24 | _container2['default'].registerDependencies.apply(_container2['default'], [target].concat(_slice.call(_arguments))); 25 | } 26 | }; 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Armen Harutyunyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dependency-injection-es6", 3 | "main": "index", 4 | "version": "1.2.1", 5 | "author": "Armen Harutyunyan ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "node out/index", 9 | "build": "babel -D --stage 0 --out-dir out ./src", 10 | "watch": "babel -D --watch --stage 0 --out-dir lib ./src", 11 | "watch-test": "babel -D --watch --stage 0 --out-dir out ./test" 12 | }, 13 | "keywords": [ 14 | "dependency injection", 15 | "di", 16 | "babel", 17 | "es6", 18 | "ecmascript 6" 19 | ], 20 | "devDependencies": { 21 | "babel": "^5.8.23", 22 | "babel-core": "^5.8.23", 23 | "babel-loader": "^5.3.2" 24 | }, 25 | "description": "dependency-injection-es6 is a dependency injection library for Node.js and Javascript environments where ES6 is supported.", 26 | "directories": { 27 | "test": "test" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/mench/dependency-injection-es6.git" 32 | }, 33 | "bugs": { 34 | "url": "https://github.com/mench/dependency-injection-es6/issues" 35 | }, 36 | "homepage": "https://github.com/mench/dependency-injection-es6#readme" 37 | } 38 | -------------------------------------------------------------------------------- /src/container.js: -------------------------------------------------------------------------------- 1 | 2 | const bindings = new Map(); 3 | const singletons = new Map(); 4 | const DEPENDENCIES = Symbol('DEPENDENCIES'); 5 | 6 | export default class Container { 7 | 8 | static bind(iClass,Class,options){ 9 | bindings.set(iClass,Class); 10 | if(options && options.singleton){ 11 | this.registerAsSingleton(Class); 12 | } 13 | } 14 | 15 | static getInstanceOf(clazz){ 16 | if(bindings.has(clazz)){ 17 | clazz = bindings.get(clazz); 18 | return this.resolve(clazz); 19 | } 20 | return this.resolve(clazz); 21 | } 22 | 23 | static resolve(clazz){ 24 | if(singletons.has(clazz)) { 25 | return this.resolveSingleton(clazz); 26 | } 27 | return this.resolveInstance(clazz); 28 | 29 | } 30 | 31 | static resolveInstance(clazz){ 32 | if(typeof clazz !="function") throw new Error(`${clazz} must be class not a ${typeof clazz}`); 33 | let classes = clazz[DEPENDENCIES] || []; 34 | let dependencies = classes.map(this.getInstanceOf.bind(this)); 35 | return new clazz(...dependencies); 36 | } 37 | 38 | 39 | 40 | static registerAsSingleton(clazz) { 41 | if(!singletons.has(clazz)) { 42 | singletons.set(clazz, null); 43 | } 44 | } 45 | 46 | static resolveSingleton(clazz) { 47 | if(singletons.get(clazz) === null) { 48 | singletons.set(clazz, this.resolveInstance(clazz)); 49 | } 50 | return singletons.get(clazz); 51 | } 52 | 53 | static registerDependencies(clazz, ...dependencies) { 54 | clazz[DEPENDENCIES] = dependencies 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import {container,inject,singleton} from '../index'; 2 | 3 | class SimpleClass{ 4 | member = "test"; 5 | } 6 | 7 | class Config { 8 | get(){} 9 | } 10 | 11 | class ConfigImpl extends Config{ 12 | 13 | @inject(SimpleClass) 14 | simple:SimpleClass; 15 | 16 | name = "Test"; 17 | constructor(){ 18 | console.info("call constructor"); 19 | super(); 20 | } 21 | 22 | get(){ 23 | console.info("get config"); 24 | } 25 | } 26 | 27 | 28 | class Service { 29 | 30 | @inject(Config) 31 | config:Config; 32 | 33 | sendMessage(){ 34 | console.info("Service:send"); 35 | } 36 | } 37 | 38 | @singleton 39 | class DevService extends Service{ 40 | constructor(){ 41 | console.info("constructor"); 42 | super(); 43 | } 44 | sendMessage(){ 45 | console.info("send msg:") 46 | } 47 | } 48 | 49 | 50 | class ProdService extends Service{ 51 | 52 | } 53 | 54 | 55 | @inject(Config) 56 | class MyDependency1 { 57 | constructor(config){ 58 | console.info("constructor MyDependency1",config) 59 | } 60 | 61 | } 62 | 63 | @inject(SimpleClass) 64 | class MyDependency2 { 65 | constructor(simple){ 66 | console.info("constructor MyDependency2",simple) 67 | } 68 | } 69 | 70 | @inject(MyDependency1,MyDependency2) 71 | class HasDependencies { 72 | 73 | @inject(Service) 74 | service:Service; 75 | 76 | @inject(Config) 77 | config:Service; 78 | 79 | constructor(myDep1,myDep2){ 80 | console.info('args',arguments) 81 | this.service.sendMessage(); 82 | console.info(myDep1,myDep2); 83 | } 84 | } 85 | 86 | 87 | class Test{ 88 | 89 | @inject(Service) 90 | service:Service; 91 | 92 | } 93 | 94 | 95 | container.bind(Service,DevService); 96 | container.bind(Config,ConfigImpl,{singleton:true}); 97 | 98 | var instance = container.resolve(HasDependencies); 99 | instance.service.sendMessage(); 100 | var test = container.resolve(Test); 101 | var service = container.getInstanceOf(Service); 102 | console.info(service); 103 | console.info("instance= ",instance); 104 | -------------------------------------------------------------------------------- /lib/container.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var _bind = Function.prototype.bind; 7 | 8 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 9 | 10 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } } 11 | 12 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 13 | 14 | var bindings = new Map(); 15 | var singletons = new Map(); 16 | var DEPENDENCIES = Symbol('DEPENDENCIES'); 17 | 18 | var Container = (function () { 19 | function Container() { 20 | _classCallCheck(this, Container); 21 | } 22 | 23 | _createClass(Container, null, [{ 24 | key: "bind", 25 | value: function bind(iClass, Class, options) { 26 | bindings.set(iClass, Class); 27 | if (options && options.singleton) { 28 | this.registerAsSingleton(Class); 29 | } 30 | } 31 | }, { 32 | key: "getInstanceOf", 33 | value: function getInstanceOf(clazz) { 34 | if (bindings.has(clazz)) { 35 | clazz = bindings.get(clazz); 36 | return this.resolve(clazz); 37 | } 38 | return this.resolve(clazz); 39 | } 40 | }, { 41 | key: "resolve", 42 | value: function resolve(clazz) { 43 | if (singletons.has(clazz)) { 44 | return this.resolveSingleton(clazz); 45 | } 46 | return this.resolveInstance(clazz); 47 | } 48 | }, { 49 | key: "resolveInstance", 50 | value: function resolveInstance(clazz) { 51 | if (typeof clazz != "function") throw new Error(clazz + " must be class not a " + typeof clazz); 52 | var classes = clazz[DEPENDENCIES] || []; 53 | var dependencies = classes.map(this.getInstanceOf.bind(this)); 54 | return new (_bind.apply(clazz, [null].concat(_toConsumableArray(dependencies))))(); 55 | } 56 | }, { 57 | key: "registerAsSingleton", 58 | value: function registerAsSingleton(clazz) { 59 | if (!singletons.has(clazz)) { 60 | singletons.set(clazz, null); 61 | } 62 | } 63 | }, { 64 | key: "resolveSingleton", 65 | value: function resolveSingleton(clazz) { 66 | if (singletons.get(clazz) === null) { 67 | singletons.set(clazz, this.resolveInstance(clazz)); 68 | } 69 | return singletons.get(clazz); 70 | } 71 | }, { 72 | key: "registerDependencies", 73 | value: function registerDependencies(clazz) { 74 | for (var _len = arguments.length, dependencies = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 75 | dependencies[_key - 1] = arguments[_key]; 76 | } 77 | 78 | clazz[DEPENDENCIES] = dependencies; 79 | } 80 | }]); 81 | 82 | return Container; 83 | })(); 84 | 85 | exports["default"] = Container; 86 | module.exports = exports["default"]; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dependency-injection-es6 2 | 3 | dependency-injection-es6 is a dependency injection library for Node.js and Javascript environments where ES6 is supported. 4 | 5 | Dependency injection is a software design pattern that implements inversion of control for resolving dependencies. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. 6 | 7 | ## Installation 8 | 9 | ```sh 10 | $ npm install --save dependency-injection-es6 11 | ``` 12 | 13 | ## Requirements 14 | 15 | dependency-injection-es6 requires a Javascript environment with ES6 classes and decorators. Babel is included as a dependency to make the library compatible 16 | with environments that do not support these features directly. 17 | 18 | ## Getting Started 19 | 20 | For resolving dependencies of classes you will need to use @inject decorator and container class to resolve instance 21 | 22 | ```javascript 23 | import {container,inject,singleton} from 'dependency-injection-es6'; 24 | 25 | class SameClass {} 26 | 27 | class App { 28 | 29 | @inject(SameClass) 30 | config:SameClass; 31 | } 32 | ``` 33 | 34 | ### Example 35 | 36 | #### Service Classes 37 | 38 | ```javascript 39 | 40 | class MessageService { 41 | 42 | sendMessage(msg:String){} 43 | 44 | } 45 | ``` 46 | 47 | ```javascript 48 | import {singleton} from 'dependency-injection-es6'; 49 | 50 | @singleton 51 | class EmailService extends MessageService { 52 | 53 | sendMessage(msg:String){ 54 | console.info("Email Message sent from EmailService"); 55 | } 56 | } 57 | ``` 58 | 59 | EmailService is one of the implementations of MessageService. Notice that class is annotated with 60 | @singleton annotation. Since service objects will be created through injector classes, this annotation is provided to let them know that the service classes are singleton objects. 61 | 62 | We have another service implementation to send facebook messages. 63 | 64 | ```javascript 65 | import {inject} from 'dependency-injection-es6'; 66 | 67 | class FacebookService extends MessageService { 68 | 69 | sendMessage(msg:String){ 70 | console.info("Message sent to Facebook user from FacebookService"); 71 | } 72 | } 73 | ``` 74 | 75 | #### Consumer Class 76 | 77 | ```javascript 78 | import {inject} from 'dependency-injection-es6'; 79 | import MessageService from './MessageService'; 80 | 81 | class MyApplication { 82 | 83 | @inject(MessageService) 84 | service:MessageService; 85 | 86 | constructor(){ 87 | console.info("will be injected",this.service); 88 | } 89 | } 90 | ``` 91 | 92 | #### Bindings 93 | 94 | ```javascript 95 | import {container} from 'dependency-injection-es6'; 96 | 97 | //bind the service to implementation class 98 | container.bind(MessageService,EmailService); 99 | 100 | //or bind MessageService to Facebook Message implementation 101 | //container.bind(MessageService,FacebookService); 102 | 103 | //make a singleton with option {singleton:true} 104 | //container.bind(MessageService,MessageService,{singleton:true}); 105 | 106 | 107 | ``` 108 | 109 | #### Resolve instance 110 | 111 | ```javascript 112 | 113 | var instance = container.resolve(MyApplication); 114 | instance.service.sendMessage("Hello"); 115 | 116 | ``` 117 | 118 | You can get injected class instance by calling getInstanceOf() method. 119 | 120 | ```javascript 121 | import {container,inject} from 'dependency-injection-es6'; 122 | 123 | class Config { 124 | get(){ return {/** .. **/}} 125 | } 126 | 127 | class Service { 128 | 129 | @inject(Config) 130 | config:Config; 131 | } 132 | 133 | class App{ 134 | 135 | @inject(Service) 136 | service:Service; 137 | } 138 | 139 | var service = container.getInstanceOf(Service); 140 | console.info(service); 141 | 142 | ``` 143 | 144 | #### Inject with constructor. 145 | 146 | ```javascript 147 | import {container,inject} from 'dependency-injection-es6'; 148 | import {MyDependency1} from './my-dependency1'; 149 | import {MyDependency2} from './my-dependency2'; 150 | 151 | @inject(MyDependency1,MyDependency2) 152 | class MyApplication { 153 | constructor(myDependency1,myDependency2) { 154 | this.myDependency1 = myDependency1; 155 | this.myDependency2 = myDependency2; 156 | } 157 | } 158 | 159 | ``` 160 | Dependencies of a class are injected through the constructor. --------------------------------------------------------------------------------