I am new at JavaScript. I wonder how dependency injection is being implemented in JavaScript? I searched the internet but couldn\'t find anything.
Let's learn it doing a super simple real world example :)
The example class I am going to talk about here is a Printer
which needs a driver
to print something. I have demonstrated the advantages of dependency injection design pattern in 4 steps to arrive at the best solution in the end.
Case 1: no dependency injection used:
class Printer {
constructor () {
this.lcd = '';
}
/* umm! Not so flexible! */
print (text) {
this.lcd = 'printing...';
console.log (`This printer prints ${text}!`);
}
}
// Usage:
var printer = new Printer ();
printer.print ('hello');
Usage is simple, it is easy to make a new printer this way but this printer is not flexible.
Case 2: abstract the functionalities inside the print
method into a new class called Driver
:
class Printer {
constructor () {
this.lcd = '';
this.driver = new Driver ();
}
print (text) {
this.lcd = 'printing...';
this.driver.driverPrint (text);
}
}
class Driver {
driverPrint (text) {
console.log (`I will print the ${text}`);
}
}
// Usage:
var printer = new Printer ();
printer.print ('hello');
So our Printer
class is now more modular, clean and easy to understand but It is not flexible yet again. Any time you use new
keyword you are actually hard-coding something. In this case you are constructing a driver inside your Printer which in real world is an example of a printer that comes with a built-in driver that can never change!
Case 3: inject an already made driver into your printer
A better version is to inject a driver at the time we construct a printer
meaning you can make any type of printer, color or black & white, because this
time the driver is being made in isolation and outside the Printer
class and then
given (INJECTED!) into the Printer
…
class Printer {
constructor (driver) {
this.lcd = '';
this.driver = driver;
}
print (text) {
this.lcd = 'printing...';
this.driver.driverPrint (text);
}
}
class BWDriver {
driverPrint (text) {
console.log (`I will print the ${text} in Black and White.`);
}
}
class ColorDriver {
driverPrint (text) {
console.log (`I will print the ${text} in color.`);
}
}
// Usage:
var bwDriver = new BWDriver ();
var printer = new Printer (bwDriver);
printer.print ('hello'); // I will print the hello in Black and White.
Usage is now different, as a user, in order to have a printer you need to first
construct (make) a driver (of your choice!) and then pass this driver to your printer. It may seem that end user now needs to know a bit more about the system, however this structure gives them more flexibility. Users can pass ANY driver as long as valid! for example let's say we have a BWDriver
(black & white) type of driver; user can create a new driver of this type and use that to make a new printer that prints black and white.
So far so good! But what you think we can do better and what you think has still some room to address here?! I am sure you can see it too!
We are creating a new printer each time we need our printer to print with
a different driver! That is because we are passing our driver of choice to
the Printer
class at the construction time; if user wants to use another driver they need to create a new Printer with that driver. For example, if now I want to do a color print I need to do:
var cDriver = new ColorDriver ();
var printer = new Printer (cDriver); // Yes! This line here is the problem!
printer.print ('hello'); // I will print the hello in color.
Case 4: provide a setter function to set the driver of your printer at ANY TIME!
class Printer {
constructor () {
this.lcd = '';
}
setDriver (driver) {
this.driver = driver;
}
print (text) {
this.lcd = 'printing...';
this.driver.driverPrint (text);
}
}
class BWDriver {
driverPrint (text) {
console.log (`I will print the ${text} in Black and White.`);
}
}
class ColorDriver {
driverPrint (text) {
console.log (`I will print the ${text} in color.`);
}
}
// Usage:
var bwDriver = new BWDriver ();
var cDriver = new ColorDriver ();
var printer = new Printer (); // I am happy to see this line only ONCE!
printer.setDriver (bwDriver);
printer.print ('hello'); // I will print the hello in Black and White.
printer.setDriver (cDriver);
printer.print ('hello'); // I will print the hello in color.
Dependency Injection is not a really difficult concept to understand. The term may be a bit overloaded but once you have realised its purpose you will find yourself using it most of the time.