Dependency Injection for JavaScript Developers
Tags: javascript, angular, injection
Published: 2023-01-16 21:05:54

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fx2tivtevcxci89r4fk3.png)

## 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();
```

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");
```

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.