Dependency injection in JavaScript

Ramkumar Khubchandani
3 min readApr 20, 2024

--

What is Dependency Injection?

Dependency injection is a design pattern that allows you to decouple the creation and management of objects from their usage. In other words, it separates the responsibilities of creating and providing objects (dependencies) from the objects that depend on them. This promotes modularity, testability, and reusability in your code.

Why Use Dependency Injection?

  • Loose Coupling: By decoupling the creation and management of objects from their usage, you can easily swap out dependencies without affecting the rest of your code.
  • Testability: Dependency injection makes it easier to write unit tests by allowing you to inject mock or test dependencies.
  • Reusability: By separating concerns, your code becomes more modular and reusable.
  • Maintainability: Dependency injection promotes code organization and makes it easier to maintain and refactor your codebase.

Step 1: Identify Dependencies

Before implementing dependency injection, you need to identify the dependencies in your code. Dependencies are typically objects or modules that your code relies on to perform certain tasks.

Example:

// Dependency
const HttpClient = require('./HttpClient');

// Class that depends on HttpClient
class UserService {
constructor() {
this.httpClient = new HttpClient();
}

getUsers() {
return this.httpClient.get('/users');
}
}

In the above example, the UserService class depends on the HttpClient module.

Step 2: Inject Dependencies

Instead of creating dependencies within the class or module that uses them, you inject them from the outside. This can be done through constructor injection, method injection, or property injection.

Constructor Injection

In constructor injection, you pass the dependencies as arguments to the constructor of the class or module that needs them.

Example:

// Dependency
const HttpClient = require('./HttpClient');

// Class that depends on HttpClient
class UserService {
constructor(httpClient) {
this.httpClient = httpClient;
}

getUsers() {
return this.httpClient.get('/users');
}
}

// Injection
const httpClient = new HttpClient();
const userService = new UserService(httpClient);

Method Injection

In method injection, you pass the dependencies as arguments to the method that needs them.

Example:

// Dependency
const HttpClient = require('./HttpClient');

// Class that depends on HttpClient
class UserService {
getUsers(httpClient) {
return httpClient.get('/users');
}
}

// Injection
const httpClient = new HttpClient();
const userService = new UserService();
const users = userService.getUsers(httpClient);

Property Injection

In property injection, you set the dependencies as properties on the class or module that needs them.

Example:

// Dependency
const HttpClient = require('./HttpClient');

// Class that depends on HttpClient
class UserService {
setHttpClient(httpClient) {
this.httpClient = httpClient;
}

getUsers() {
return this.httpClient.get('/users');
}
}

// Injection
const httpClient = new HttpClient();
const userService = new UserService();
userService.setHttpClient(httpClient);
const users = userService.getUsers();

Step 3: Manage Dependencies

Once you’ve injected your dependencies, you need to manage their lifecycle. This can be done manually or with the help of a dependency injection framework or library.

Manual Dependency Management

In simple applications, you can manage dependencies manually by creating and injecting them yourself.

Example:

// Dependencies
const HttpClient = require('./HttpClient');
const Database = require('./Database');

// Class that depends on HttpClient and Database
class UserService {
constructor(httpClient, database) {
this.httpClient = httpClient;
this.database = database;
}

getUsers() {
return this.httpClient.get('/users');
}

saveUser(user) {
return this.database.save(user);
}
}

// Injection
const httpClient = new HttpClient();
const database = new Database();
const userService = new UserService(httpClient, database);

Using a Dependency Injection Framework or Library

For larger applications, you might want to consider using a dependency injection framework or library, such as InversifyJS, TypeDI, or bottlejs. These tools provide a more structured way to manage dependencies and can simplify the injection process.

Example (using InversifyJS):

// Dependencies
const HttpClient = require('./HttpClient');
const Database = require('./Database');

// Class that depends on HttpClient and Database
class UserService {
constructor(httpClient, database) {
this.httpClient = httpClient;
this.database = database;
}

getUsers() {
return this.httpClient.get('/users');
}

saveUser(user) {
return this.database.save(user);
}
}

// Dependency Injection Container
const { Container } = require('inversify');
const container = new Container();
container.bind('HttpClient').to(HttpClient);
container.bind('Database').to(Database);
container.bind('UserService').to(UserService);

// Injection
const userService = container.get('UserService');

In this example, we use InversifyJS to set up a dependency injection container. We bind our dependencies (HttpClient and Database) and the class that needs them (UserService) to the container. Then, we can request an instance of UserService from the container, and it will automatically inject the required dependencies.

These are just a few examples of how dependency injection can be implemented in JavaScript. The specific approach you choose will depend on the complexity of your application and your personal preferences.

Want to learn more about such things ?

Follow me or message me on Linkedin.

--

--

Ramkumar Khubchandani
Ramkumar Khubchandani

Written by Ramkumar Khubchandani

Frontend Developer|Technical Content Writer|React|Angular|React-Native|Corporate Trainer|JavaScript|Trainer|Teacher| Mobile: 7709330265|ramkumarkhub@gmail.com

No responses yet