Honestly, coming from PHP, I really don't like the way dependencies are handled in JavaScript.
Using require()
or import
either gives me a singleton object, or a class that I have to
instantiate myself.
The DI container in Laravel is wonderful, and allows you to basically just ask for a dependency in a class constructor and it hands it to you. You can bind things to the container, but you can also resolve things without explicitly binding them, which I think is awesome.
I made this small example just to see whether the Laravel practices work in JavaScript.
Perhaps it violates the fundamental idea of JavaScript, who knows, but I'd love if frameworks
like Ember and React worked like this out of the box. (I mean, how nice isn't this compared to React's context
?)
Binding to the container:
// Import the container instance
import app from './Container';
// Import some classes
import Service from './Service';
import ReportService from './ReportService';
// Bind to the container using a function, with a string as the key...
app.bind('service', () => {
return new ReportService();
});
// ... or with a class as the key
app.bind(Service, () => {
return new ReportService();
})
// Bind directly to a class
app.bind('service', ReportService);
app.bind(Service, ReportService);
// Bind as a singleton to recieve the same instance every time it's resolved
app.singleton(Service, () => {
return new ReportService();
})
app.singleton('service', ReportService);
// Tell the container to resolve the same instance every time Service is asked for
app.singleton(Service);
// Bind an existing instance to the container
app.instance('service', new ReportService());
app.instance(Service, new ReportService());
Resolving from the container:
// Import the container instance
import app from './Container';
// Resolve an item from the container using familiar Laravel syntax
let service = app.make('service');
// If Service isn't bound to the container, it will instantiate it for you,
// along with all of its dependencies (see below)
let service = app.make(Service);
Resolving, Laravel-fu style with ES7 decorators:
// Import the container instance
import app, { depends } from './Container';
// Import some classes
import MailMan from 'mail-man'; // or whatever
import ReportService from './ReportService';
// Make a class with constructor dependencies. We have to use something
// to tell the container which class to look up. In PHP we have type hinting,
// but JavaScript doesn't. I think using decorators makes it as nice as it gets.
// You can also do Mailer.depends = [MailMan]; if that's your style.
@depends(MailMan)
class Mailer {
constructor(mailMan) {
this.mailMan = mailMan;
}
sendToAdmin(text) {
this.mailMan.send('[email protected]', text);
}
}
// Another class which depends on our previous class
@depends(ReportService, Mailer)
class Dashboard {
constructor(reports, mailer) {
this.reports = reports;
this.mailer = mailer;
}
generateReport() {
let report = this.reports.daily();
this.mailer.sendToAdmin(report.body);
return report;
}
}
// And finally, we just have to get the ball rolling
let dashboard = app.make(Dashboard);
let report = dashboard.generateReport();
If you don't have the luxury of instantiating your classes yourself (maybe your framework does it for you), you can use the @inject
decorator to inject dependencies directly to the class prototype. Coming from Laravel, it feels a bit hacky, but hey, it's better than nothing.
// Import the container instance
import app, { inject } from './Container';
// Import some classes
import ReportService from './ReportService';
import Mailer from './Mailer';
@inject({ reports: ReportService, mailer: Mailer })
class Dashboard {
generateReport() {
let report = this.reports.daily();
this.mailer.sendToAdmin(report.body);
return report;
}
}