DEV Community

Cover image for Dependency Injection for JavaScript Developers
Branch
Branch

Posted on

Dependency Injection for JavaScript Developers

Have you got any experience in Angular or Spring?
Are you familiar with Angular or Spring?
If not, don't worry.
Here in this blog, I am here with you to let you feel more confident about DI.

In this article, you’ll learn what dependency injection is, when you should use it, and what popular JavaScript frameworks it’s implemented in.

What is dependency injection?

First, you have to understand what the dependency is.
It's not you can see in the dependency of package.json.

I am sure you don't have any conflicts about that.
It's just the case from my friend.

Let's get started.

So before getting to dependency injections, first let’s understand what a dependency in programming means.

When class A uses some functionality of class B, then its said that class A has a dependency of class B.

In Java, before we can use methods of other classes, we first need to create the object of that class (i.e. class A needs to create an instance of class B).

So, transferring the task of creating the object to someone else and directly using the dependency is called dependency injection.

Dependency Injection in humor

Why do we need dependency injection?

Here, I prepared 2 examples to let you have clear understanding of DI.

Example 1

Let’s say we have a car class which contains various objects such as wheels, engine, etc.

Here the car class is responsible for creating all the dependency objects. Now, what if we decide to ditch MRFWheels in the future and want to use Yokohama Wheels?

We will need to recreate the car object with a new Yokohama dependency. But when using dependency injection (DI), we can change the Wheels at runtime (because dependencies can be injected at runtime rather than at compile time).

You can think of DI as the middleman in our code who does all the work of creating the preferred wheels object and providing it to the Car class.

It makes our Car class independent from creating the objects of Wheels, Battery, etc.

Example 2

For instance, consider how video game consoles only need a compatible disc or cartridge to function. Different discs or cartridges usually contain information about different games. The gamer doesn’t need to know anything about the console’s internals and usually doesn’t do anything other than insert or replace game discs in order to play a game.

Try to represent a console’s functionality with code. In this example, you’ll call your console the NoSleepStation, and you’ll work with the following assumptions:

A NoSleepStation console can only play games designed for the NoSleepStation.
The only valid input source for the console is a compact disc.
With this information, one could implement the NoSleepStation console in the following way:

// The GameReader class
class GameReader {
  constructor(input) {
    this.input = input;
  }
  readDisc() {
    console.log("Now playing: ", this.input);
  }
}

// The NoSleepStation Console class
class NSSConsole {
  gameReader = new GameReader("TurboCars Racer");

  play() {
    this.gameReader.readDisc();
  }
}

// use the classes above to play 
const nssConsole = new NSSConsole();
nssConsole.play();
Enter fullscreen mode Exit fullscreen mode

Here, the core console logic is in the GameReader class, and it has a dependent, the NSSConsole. The console’s play method launches a game using a GameReader instance. However, a few problems are evident here, including flexibility and testing.

Flexibility

The previously mentioned code is inflexible. If a user wanted to play a different game, they would have to modify the NSSConsole class, which is similar to taking apart the console in real life. This is because a core dependency, the GameReader class, is hard coded into the NSSConsole implementation.

Dependency injection addresses this problem by decoupling classes from their dependencies, only providing these dependencies when they are needed. In the previous code sample, all that the NSSConsole class really needs is the readDisc() method from a GameReader instance.

With dependency injection, the previous code can be rewritten like this:

class GameReader {
  constructor(input) {
    this.input = input;
  }

  readDisc() {
    console.log("Now playing: ", this.input);
  }

  changeDisc(input) {
    this.input = input;
    this.readDisc();
  }
}

class NSSConsole {
  constructor(gameReader) {
    this.gameReader = gameReader;
  }

  play() {
    this.gameReader.readDisc();
  }

  playAnotherTitle(input) {
    this.gameReader.changeDisc(input);
  }
}

const gameReader = new GameReader("TurboCars Racer");
const nssConsole = new NSSConsole(gameReader);

nssConsole.play();
nssConsole.playAnotherTitle("TurboCars Racer 2");
Enter fullscreen mode Exit fullscreen mode

The most important change in this code is that the NSSConsole and GameReader classes have been decoupled. While an NSSConsole still needs a GameReader instance to function, it doesn’t have to explicitly create one. The task of creating the GameReader instance and passing it to the NSSConsole is left to the dependency injection provider.

In a large codebase, this can significantly help to reduce code boilerplate since the work of creating and wiring up dependencies is handled by a dependency injection provider. This means you don’t need to worry about creating instances of the classes that a certain class needs, especially if those dependencies have their own dependencies.

So I hope you get a little understanding why dependency injection is important.

But there is nothing perfect.

Let's see what could be cons of DI.

Cons of Dependency Injection.

It’s important to note that using dependency injection is not without its disadvantages, which are usually related to how dependency injection attempts to solve the problem of reducing code boilerplate.

In order to get the most from dependency injection, especially in a large codebase, a dependency injection provider has to be set up. Setting up this provider can be a lot of work and is sometimes unnecessary overhead in a simple project.

This problem can be solved by using a dependency injection library or framework. However, using a dependency injection framework requires a total buy-in in most cases, as there aren’t many API similarities between different dependency injection frameworks during configuration.

So here, I will finish the first part of my blog about DI.
Thank you all and wait for my next blog which will cover about dependency injection in popular Javascript frameworks and Javascript dependency injection frameworks.

Top comments (0)