UNPKG

didi

Version:

Dependency Injection for JavaScript

271 lines (178 loc) 6.63 kB
# `didi` [![CI](https://github.com/nikku/didi/actions/workflows/CI.yml/badge.svg)](https://github.com/nikku/didi/actions/workflows/CI.yml) A tiny [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) container for JavaScript. ## About Using [`didi`](https://github.com/nikku/didi) you follow the [dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) / [inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control) pattern, decoupling component declaration from instantiation. Once declared, `didi` instantiates components as needed, transitively resolves their dependencies, and caches instances for re-use. ## Example ```ts import { Injector } from 'didi'; function Car(engine) { this.start = function() { engine.start(); }; } function createPetrolEngine(power) { return { start: function() { console.log('Starting engine with ' + power + 'hp'); } }; } // define a (didi) module - it declares available // components by name and specifies how these are provided const carModule = { // asked for 'car', the injector will call new Car(...) to produce it 'car': ['type', Car], // asked for 'engine', the injector will call createPetrolEngine(...) to produce it 'engine': ['factory', createPetrolEngine], // asked for 'power', the injector will give it number 1184 'power': ['value', 1184] // probably Bugatti Veyron }; // instantiate an injector with a set of (didi) modules const injector = new Injector([ carModule ]); // use the injector API to retrieve components injector.get('car').start(); // alternatively invoke a function, injecting the arguments injector.invoke(function(car) { console.log('started', car); }); // if you work with a TypeScript code base, retrieve // a typed instance of a component const car: Car = injector.get<Car>('car'); car.start(); ``` For real-world examples, check out [Karma](https://github.com/karma-runner/karma) or [diagram-js](https://github.com/bpmn-io/diagram-js), two libraries that heavily use dependency injection at their core. You can also check out [the tests](https://github.com/nikku/didi/blob/master/test/injector.spec.js) to learn about all supported use cases. ## Usage Learn how to [declare](#declaring-components), [inject](#injecting-components) and [initialize](#initializing-components) your components. ### Declaring Components By declaring a component as part of a `didi` module, you make it available to other components. #### `type(token, Constructor)` `Constructor` will be called with `new` operator to produce the instance: ```js const module = { 'engine': ['type', DieselEngine] }; ``` #### `factory(token, factoryFn)` The injector produces the instance by calling `factoryFn` without any context. It uses the factory's return value: ```js const module = { 'engine': ['factory', createDieselEngine] }; ``` #### `value(token, value)` Register a static value: ```js const module = { 'power': ['value', 1184] }; ``` ### Injecting Components The injector looks up dependencies based on explicit annotations, comments, or function argument names. #### Argument Names If no further details are provided the injector parses dependency names from function arguments: ```js function Car(engine, license) { // will inject components bound to 'engine' and 'license' } ``` #### Function Comments You can use comments to encode names: ```js function Car(/* engine */ e, /* x._weird */ x) { // will inject components bound to 'engine' and 'x._weird' } ``` #### `$inject` Annotation You can use a static `$inject` annotation to declare dependencies in a minification safe manner: ```js function Car(e, license) { // will inject components bound to 'engine' and 'license' } Car.$inject = [ 'engine', 'license' ]; ``` #### Array Notation You can also the minification save array notation known from [AngularJS][AngularJS]: ```js const Car = [ 'engine', 'trunk', function(e, t) { // will inject components bound to 'engine' and 'trunk' }]; ``` #### Partial Injection Sometimes it is helpful to inject only a specific property of some object: ```js function Engine(/* config.engine.power */ power) { // will inject 1184 (config.engine.power), // assuming there is no direct binding for 'config.engine.power' token } const engineModule = { 'config': ['value', {engine: {power: 1184}, other : {}}] }; ``` ### Initializing Components Modules can use an `__init__` hook to declare components that shall eagerly load or functions to be invoked, i.e., trigger side-effects during initialization: ```javascript import { Injector } from 'didi'; function HifiComponent(events) { events.on('toggleHifi', this.toggle.bind(this)); this.toggle = function(mode) { console.log(`Toggled Hifi ${mode ? 'ON' : 'OFF'}`); }; } const injector = new Injector([ { __init__: [ 'hifiComponent' ], hifiComponent: [ 'type', HifiComponent ] }, ... ]); // initializes all modules as defined injector.init(); ``` ### Overriding Components You can override components by name. That can be beneficial for testing but also for customizing: ```js import { Injector } from 'didi'; import coreModule from './core'; import HttpBackend from './test/mocks'; const injector = new Injector([ coreModule, { // overrides already declared `httpBackend` httpBackend: [ 'type', HttpBackend ] } ]); ``` ### Type-safety [`didi`](https://github.com/nikku/didi) ships type declarations that allow you to use it in a type safe manner. #### Explicit Typing Pass a type attribute to `Injector#get` to retrieve a service as a known type: ```typescript const hifiComponent = injector.get<HifiComponent>('hifiComponent'); // typed as <HifiComponent> hifiComponent.toggle(); ``` #### Implicit Typing Configure the `Injector` through a service map and automatically cast services to known types: ```typescript type ServiceMap = { 'hifiComponent': HifiComponent }; const injector = new Injector<ServiceMap>(...); const hifiComponent = injector.get('hifiComponent'); // typed as <HifiComponent> ``` ## Credits This library builds on top of the (now unmaintained) [node-di][node-di] library. `didi` is a maintained fork that adds support for ES6, the minification safe array notation, and other features. ## Differences to [node-di][node-di] - supports array notation - supports [ES2015](http://babeljs.io/learn-es2015/) - bundles type definitions - module initialization + module dependencies ## License MIT [AngularJS]: http://angularjs.org/ [node-di]: https://github.com/vojtajina/node-di