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 install --save node-dependency-injectionYou 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 MailerYou 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 MailerThen 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 NewsletterManagerWhen 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')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:
services:
app.mailer:
# ...
app.newsletter_manager:
class: ./NewsletterManager
calls:
- [setMailer, ['@app.mailer']]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)Another possibility is just setting public fields of the class directly:
class NewsletterManager {
/**
* @param {Mailer} mailer
*/
set mailer (value) {
this._mailer = value
}
}services:
# ...
app.newsletter_manager:
class: ./App/Mail/NewsletterManager
properties:
mailer: '@mailer'import {Definition, Reference} from 'node-dependency-injection'
// ...
definition = new Definition(NewsletterManager)
definition.addProperty('mailer', new Reference('mailer'))A Node Dependency Injection Middleware for Express
npm install --save node-dependency-injection-express-middlewareimport 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())