Inversion of Control and Dependency Injection in typescript

Inversion of Control and Dependency Injection in typescript

In this article, we will implement Inversion of Control (IoC ) using a technique called Dependency Injection (DI) which allows injecting the dependent objects into the class rather than having the class generate them.

According to the design principle "Inversion of Control", an external entity is given control over a program's flow instead of the main programme. Put another way, we cede control to someone else in exchange for their ability to give us the dependencies, tools and capabilities that we require.

Dependency Injection examples

Example without DI

Without Dependency Injection we will have the concrete Logger implementation and Services closely connected and depends on each other:

import { ConsoleLogger } from "./console.logger";

export class Service {
   private logger: any;
   constructor() {
       this.logger = new ConsoleLogger();
   }
   // ...
}

There is a hard-coded dependency of the ConsoleLogger implementation making it not flexible and tightly couple.

Example of Constructor Injection

In this example the dependency is injected through the class constructor declaring the dependency as an interface type:

import { ILogger } from "./logger.interface";

export class Service {
    // Constructor Injection
    constructor(private logger: ILogger) { }
    // ...
}

Since the Service class in our example receives the Logger instance through its constructor, changing or mocking the dependence in various scenarios is made simpler:

import { ConsoleLogger } from "./console.logger";

class Main {
  constructor() {
    // Manually arrange the concrete logger implementation
    const logger = new ConsoleLogger();
    // Pass the console logger instance to the service constructor
    const service = new Service(logger);
    // ...
  }
}

Advantages

  • Loose coupling between classes and their dependencies

  • Systems has reusable, testable and maintainable components

  • Easy to test: Easier to mock the components and provided it as a dependencies.

  • Refactor or replace components: Just change the code implementation without touch the dependency's consumers.

  • Pluggable components: Easy to swap between libraries or concrete implementations.

Disadvantages

  • Makes the code hard to understand due to the higher DI learning curve.

  • Increases the project size: Increasing the number of classes or components since the DI promotes a clearly separation of responsibilities.

In conclusion, IoC is a design principle that grants external entities control over a program's flow, while DI is a method for putting IoC into practice by injecting dependencies into a class.

See you in the next article.

Did you find this article valuable?

Support Señor Developer by becoming a sponsor. Any amount is appreciated!