/node-dependency-injection

The NodeDependencyInjection component allows you to standarize and centralize the way objects are constructed in your application.

Primary LanguageJavaScriptMIT LicenseMIT

Node Dependency Injection

NDI Logo

A special thanks to Symfony which was a great inspiration and example for this project.

The Node Dependency Injection component allows you to standardize and centralize the way objects are constructed in your application.

Npm Version Build Status Dependencies DevDependencies Code Coverage Code Climate Coding Standard Known Vulnerabilities Npm Downloads License

Installation

npm install --save node-dependency-injection

Usage

You might have a simple class like the following Mailer that you want to make available as a service:

class Mailer {
    constructor () {
        this._transport = 'sendmail'
    }
}

export default Mailer

You can register this in the container as a service:

import {ContainerBuilder} from 'node-dependency-injection'
import Mailer from './Mailer'

let container = new ContainerBuilder()
container.register('mailer', Mailer)

An improvement to the class to make it more flexible would be to allow the container to set the transport used. If you change the class so this is passed into the constructor:

class Mailer {
    constructor (transport) {
        this._transport = tansport
    }
}

export default Mailer

Then you can set the choice of transport in the container:

import {ContainerBuilder} from 'node-dependency-injection'
import Mailer from './Mailer'

let container = new ContainerBuilder()
container
  .register('mailer', Mailer)
  .addArgument('sendmail')

This class is now much more flexible as you have separated the choice of transport out of the implementation and into the container.

Now that the mailer service is in the container you can inject it as a dependency of other classes. If you have a NewsletterManager class like this:

class NewsletterManager {
    construct (mailer, fs) {
        this._mailer = mailer
        this._fs = fs
    }
}

export default NewsletterManager

When defining the newsletter_manager service, the mailer service does not exist yet. Use the Reference class to tell the container to inject the mailer service when it initializes the newsletter manager:

import {ContainerBuilder, Reference, PackageReference} from 'node-dependency-injection'
import Mailer from './Mailer'
import NewsletterManager from './NewsletterManager'

let container = new ContainerBuilder()

container
  .register('mailer', Mailer)
  .addArgument('sendmail')

container
  .register('newsletter_manager', NewsletterManager)
  .addArgument(new Reference('mailer'))
  .addArgument(new PackageReference('fs-extra'))

If the NewsletterManager did not require the Mailer and injecting it was only optional then you could use setter injection instead:

class NewsletterManager {
    setMailer (mailer) {
        this._mailer = mailer
    }

    // ...
}

You can now choose not to inject a Mailer into the NewsletterManager. If you do want to though then the container can call the setter method:

import {ContainerBuilder, Reference, PackageReference} from 'node-dependency-injection'
import Mailer from './Mailer'
import NewsletterManager from './NewsletterManager'

let container = new ContainerBuilder()

container
    .register('mailer', Mailer)
    .addArgument('sendmail')

container
    .register('newsletter_manager', NewsletterManager)
    .addMethodCall('setMailer', [new Reference('mailer')])

You could then get your newsletter_manager service from the container like this:

import {ContainerBuilder} from 'node-dependency-injection'

let container = new ContainerBuilder()
// ...

let newsletterManager = container.get('newsletter_manager')

Optional Dependencies: Setter Injection

Injecting dependencies into the constructor in this manner is an excellent way of ensuring that the dependency is available to use. If you have optional dependencies for a class, then "setter injection" may be a better option. This means injecting the dependency using a method call rather than through the constructor. The class would look like this:

import Mailer from './Mailer'

class NewsletterManager {
  constructor {
    this._mailer = null
  }

  /**
   * @param {Mailer} mailer
   */
  setMailer(mailer) {
    this.mailer = mailer;
  }

  // ...
}

Injecting the dependency by the setter method just needs a change of syntax:

YAML
services:
    app.mailer:
        # ...

    app.newsletter_manager:
        class: ./NewsletterManager
        calls:
            - [setMailer, ['@app.mailer']]
JS
import {Reference, Definition} from 'node-dependency-inection'
import NewsletterManager from './Service/NewsletterManager'

// ...

definition = new Definition(NewsletterManager)
definition.addMethodCall('setMailer', [new Reference('app.mailer')])

container.setDefinition('app.newsletter_manager', definition)

Property Injection

Another possibility is just setting public fields of the class directly:

class NewsletterManager {
     /**
      * @param {Mailer} mailer
      */
     set mailer (value) {
          this._mailer = value
     }
}
YAML
services:
     # ...

     app.newsletter_manager:
         class: ./App/Mail/NewsletterManager
         properties:
             mailer: '@mailer'
JS
import {Definition, Reference} from 'node-dependency-injection'

// ...

definition = new Definition(NewsletterManager)
definition.addProperty('mailer', new Reference('mailer'))

ExpressJS Usage

A Node Dependency Injection Middleware for Express

npm install --save node-dependency-injection-express-middleware
import NDIMiddleware from 'node-dependency-injection-express-middleware'
import express from 'express'

const app = express()

const options = {serviceFilePath: 'some/path/to/config.yml'}
app.use(new NDIMiddleware(options).middleware())

Express Middleware Documentation

Resources