Damiรกn Pumar

← Back to blog

Published on January 1, 2024 by Damiรกn Pumar

What is dependencies injection? ๐Ÿ’‰

Dependencies injection is a design pattern that is used to create objects dependent on other objects. Instead of creating dependent objects within the class that uses them, we pass them through the constructor or a method.

What is an dependencies injection container? ๐Ÿ“ฆ

A dependency injection container is a component of our application that knows how to create and inject dependencies. The dependency injection container is responsible for instantiating classes, resolving their dependencies, and providing an instance of the class when requested.

Why use a dependency injection container? ๐Ÿค”

The main reason to use a dependency injection container is to reduce the coupling between classes. The dependency injection container is responsible for creating and injecting dependencies, so the class that uses them does not need to know how to create them.

Advantages ๐Ÿ‘Œ

In short, using dependency injection and particularly using a dependency injection container allows us to create more flexible, maintainable and testable applications.

Disadvantages ๐Ÿ‘Ž

How to implement a dependency injection container in frontend? ๐Ÿค”

In frontend we have many options to implement a dependency injection container, in this post we will see how to implement it in a Typescript application, but you can implement it in any frontend framework or library, or in vanilla javascript or even on any backend application.

For this occasion we will use ts-injecty a dependency injection container that I have created myself, but you can use any other dependency injection container.

ts-injecty ๐Ÿ’‰

Take a look ๐Ÿ™Œ https://github.com/damianpumar/ts-injecty

Advantages of ts-injecty ๐Ÿ‘Œ

Installation ๐Ÿ“ฆ

npm install ts-injecty --save

Index ๐Ÿ“–

Instantiate objects without dependencies

class A {
  public sum(x: number, y: number) {
    return x + y;
  }
}

const dependencies = [register(A).build()];

Container.register(dependencies);

const a = Container.resolve(A);

expect(a.sum(1, 2)).toBe(3);

Instantiate objects with one dependencies

Now letโ€™s see how we can inject an object that depends on another object, in this case A depends on B. With ts-injecty we can do it in the following way, super simple ๐Ÿ‘‡.

class A {
  constructor(private b: B) {}

  public sum(x: number, y: number) {
    return this.b.sum(x, y);
  }
}

class B {
  public sum(x: number, y: number) {
    return x + y;
  }
}

Container.register([register(A).withDependency(B).build()]);

const resolved = Container.resolve(ClassWithInterfaceDependency);

expect(resolved.doSomething()).toBe("HI");

Instantiate objects with dependencies of dependencies

In this case A depends on B and B depends on C, as we can dynamically inject an object that depends on another object that in turn depends on another object, easy ๐Ÿ‘‡.

class A {
  constructor(private b: B) {}

  public sum(x: number, y: number) {
    return this.b.sum(x, y);
  }
}

class B {
  constructor(private c: C) {}

  public sum(x: number, y: number) {
    return this.c.sum(x, y);
  }
}

class C {
  public sum(x: number, y: number) {
    return x + y;
  }
}

const dependencies = [
  register(B).withDependency(C).build(),
  register(A).withDependency(B).build(),
];

Container.register(dependencies);

const a = Container.resolve(A);

expect(a.sum(1, 2)).toBe(3);

Instantiate objects with more than one dependency

In this case A depends on B and C, letโ€™s see it ๐Ÿ‘‡.

class A {
  constructor(
    private b: B,
    private c: C
  ) {}

  public sum(x: number, y: number) {
    return this.b.sum(x, y) + this.c.sum(x, y);
  }
}

class B {
  public sum(x: number, y: number) {
    return x + y;
  }
}

class C {
  public sum(x: number, y: number) {
    return x + y;
  }
}

const dependencies = [register(A).withDependencies(B, C).build()];

Container.register(dependencies);

const a = Container.resolve(A);

expect(a.sum(1, 2)).toBe(6);

As you can see, ts-injecty has changed the withDependency method to withDependencies in plural, this is because now we can inject more than one dependency. ts-injecty is smart enough to know that if we pass a single argument to withDependencies it is because we want to inject a single dependency, and if we pass more than one argument it is because we want to inject more than one dependency. In addition, this method will show us in the intellisense the type of argument that we need to pass applying type-checking at compile time.

Instantiate objects with a singleton dependency

ts-injecty by default registers dependencies as transient unless we register an implementation (we will see later).

Letโ€™s see the case where A depends on B and B is a singleton dependency ๐Ÿ‘‡.

class A {
  constructor(private b: B) {}

  public sum(x: number, y: number) {
    return this.b.sum(x, y);
  }
}

class B {
  private lastSum: number = 0;

  public sum(x: number, y: number) {
    this.lastSum += x + y;

    return this.lastSum;
  }
}

const dependencies = [
  register(B).asSingleton().build(),
  register(A).withDependency(B).build(),
];

Container.register(dependencies);

const a = Container.resolve(A);

expect(a.sum(1, 2)).toBe(3);

const a2 = Container.resolve(A);

expect(a2.sum(1, 2)).toBe(6); // 3 + 3

Instantiate objects with a interface dependency

As you know, in typescript interfaces are not transpiled, so this scenario is much simpler than you imagine, since it does not matter if the implementation of that interface is a function, a class or a react hook, ts-injecty has no problem injecting a dependency of an interface, we just need to comply with the contract agreed in the interface.

Letโ€™s see a first implementation of an interface ๐Ÿ‘‡.

interface B {
  doSomething(): string;
}

class A {
  constructor(private b: B) {}

  public doSomething() {
    return this.b.doSomething();
  }
}

const Implementation = () => {
  return {
    doSomething(): string {
      return "HI";
    },
  };
};

Container.register([register(A).withDependency(Implementation).build()]);

const resolved = Container.resolve(A);

expect(resolved).toBeInstanceOf(A);
expect(resolved.doSomething()).toBe("HI");

We could also solve this same case, when we have a class that implements the interface, the result is the same, what changes is the configuration of the dependency.

interface B {
  doSomething(): string;
}

class A {
  constructor(private b: B) {}

  public doSomething() {
    return this.b.doSomething();
  }
}

class Implementation implements B {
  public doSomething(): string {
    return "HI";
  }
}

Container.register([register(A).withDependency(Implementation).build()]);

const resolved = Container.resolve(A);

expect(resolved).toBeInstanceOf(A);
expect(resolved.doSomething()).toBe("HI");

I imagine that with all these alternatives, you already have an idea of how you can implement a dependency injection container in your frontend project.

Now, what would happen if you need to change your dependencies at runtime? with ts-injecty it is super simple, the truth is, since you only have to create a class inherit this class to a Factory that is implemented in ts-injecty and then register that class as a dependency, letโ€™s see it ๐Ÿ‘‡.

interface X {
  doSomething(): string;
}

class A {
  public doSomething() {
    return "HI";
  }
}

class B {
  public doSomething() {
    return "BYE";
  }
}

import { Factory } from "ts-injecty";

class FactoryImplementation extends Factory {
  public create(type: "A" | "B"): X {
    if (type === "A") {
      return this.resolver.resolve(A);
    }

    return this.resolver.resolve(B);
  }
}

Container.register([
  register(A).build(),
  register(B).build(),
  register(FactoryImplementation).build(),
]);

const resolved = Container.resolve(FactoryImplementation);

expect(resolved.create("A")).toBeInstanceOf(A);
expect(resolved.create("B")).toBeInstanceOf(B);

In this case, ts-injecty will automatically inject a Resolver when instantiating this FactoryImplementation that will allow us to resolve the dependencies we need at runtime.

If you liked this post, do not forget to share it with your friends and follow me in Twitter to be aware of my next posts.

Aquรญ comparto contigo un proyecto de cรณmo implementar ts-injecty en un proyecto de React, Stackblitz

Bye ๐Ÿ™Œ

Written by Damiรกn Pumar

Something wrong? Let me know ๐Ÿ™

← Back to blog