Skip to content

GettingStarted

Mauro edited this page May 27, 2022 · 6 revisions

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')

Autowire for TypeScript

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

const container = new ContainerBuilder(
  false, 
  '/path/to/src'
)
const autowire = new Autowire(container)
await autowire.process()

or from yaml-json-js configuration

# /path/to/services.yml
services:
  _defaults:
    autowire: true
    rootDir:  "/path/to/src"

You can also get a service from a class definition

import SomeService from '@src/service/SomeService'

container.get(SomeService)

If you are transpiling your Typescript may you need to dump the some kind of service configuration file.

import {ContainerBuilder, Autowire, ServiceFile} from 'node-dependency-injection'

const container = new ContainerBuilder(
  false, 
  '/path/to/src'
)
const autowire = new Autowire(container)
autowire.serviceFile = new ServiceFile('/some/path/to/dist/services.yaml')
await autowire.process()

My proposal for load configuration file in a production environment with transpiling/babel compilation:

if (process.env.NODE_ENV === 'dev') {
  this._container = new ContainerBuilder(false, '/src');
  this._autowire = new Autowire(this._container);
  this._autowire.serviceFile = new ServiceFile('/some/path/to/dist/services.yaml');
  await this._autowire.process();
} else {
  this._container = new ContainerBuilder(false, '/dist');
  this._loader = new YamlFileLoader(this._container);
  await this._loader.load('/some/path/to/dist/services.yaml');
}
await this._container.compile();

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'))