Any language now acknowledges dependency injection as a first-class citizen. Javascript(typescript) has seen a lot of development in recent years. Typescript has risen to the top of the language charts.
In this article, I’ll show you how to use a “tsyringe” to add DI to your typescript code.
TSyringe. A lightweight dependency injection container for TypeScript/JavaScript for constructor injection.
In this post, I will show how to add DI in your typescript application without decorators.
Consider the following scenario:
export interface IFoo{
doSomething():void;
}
export class FooService implements IFoo{
doSomething(): void {
console.log("Inside Foo Service");
}
}
export interface IBar {
doSomethingReal(): void;
}
export class BarService implements IBar {
doSomethingReal(): void {
console.log("Inisde Bar Service");
}
}
Let’s consume these services in our “main” application. In our case “App.ts”.
import { IBar, BarService } from "./Bar";
import { FooService, IFoo } from "./Foo";
export class App {
private _foo: IFoo;
private _bar: IBar;
constructor(foo: IFoo, bar: IBar) {
this._foo = foo;
this._bar = bar;
}
run(): void {
this._foo.doSomething();
this._bar.doSomethingReal();
}
}
const app = new App(new FooService(), new BarService());
app.run();
In App.ts, you can see that I’m manually passing the dependency.
Tight coupling is poor in almost every situation because it limits code extensibility and reusability, makes the system brittle, and makes programme adjustments extremely difficult. It makes testing more challenging as well.
By incorporating DI into our system, you can eliminate the dependency. The DI system eliminates the code’s tight coupling. You can either write your own DI or use some other publicly available DI system. I’m using the tsyringe library in this tutorial.
Let’s install tysringe by running the following command
npm install tysringe
And add @injectable
decorator on each service in out case Foo and Bar Service
@injectable()
export class BarService implements IBar {
...
}
@injectable()
export class FooService implements IFoo {
...
}
Now let’s register the dependecny to container
import "reflect-metadata";
import { container } from "tsyringe";
...
container.register("Foo",FooService);
container.register("Bar",BarService);
Then call the container.resolve
in the constructor
App.ts
import "reflect-metadata";
import { FooService, IFoo } from './Foo';
import { container } from "tsyringe";
import { BarService, IBar } from './Bar';
container.register("Foo",FooService);
container.register("Bar",BarService);
export class App {
private _fooService: IFoo;
private _barService:IBar;
constructor() {
this._fooService = container.resolve("Foo");
this._barService = container.resolve("Bar");
}
run(): void {
this._fooService.doSomething();
this._barService.doSomethingReal();
}
}
const app = new App();
app.run();
Let’s change the example little bit. Now pass the BarService into FooService
Foo.ts
import { delay, inject, injectable } from "tsyringe";
import { IBar, BarService } from "./Bar";
export interface IFoo {
doSomething(): void;
}
@injectable()
export class FooService implements IFoo {
private _barService: IBar;
constructor(barService: IBar) {
this._barService = barService;
}
doSomething(): void {
this._barService.doSomethingReal();
console.log("Inisde Foo Service");
}
}
If you try to run the application now, you’ll get the following error.
Error: Cannot inject the dependency at position #0 of “Foo” constructor. Reason:
Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try usingdelay
function.
App.ts
import "reflect-metadata";
import { FooService, IFoo } from "./Foo";
import { container } from "tsyringe";
export class App {
private _foo: IFoo;
constructor() {
this._foo = container.resolve(FooService);
}
run(): void {
this._foo.doSomething();
}
}
const app = new App();
app.run();
To solve this issue you can use the delay
function helper. The delay function wraps the constructor in an instance of DelayedConstructor.
Foo.ts
import { delay, inject, injectable } from "tsyringe";
import { IBar, BarService } from "./Bar";
export interface IFoo {
doSomething(): void;
}
@injectable()
export class FooService implements IFoo {
private _barService: IBar;
constructor(@inject(delay(() => BarService)) barService: IBar) {
this._barService = barService;
}
doSomething(): void {
this._barService.doSomethingReal();
console.log("Inisde Foo Service");
}
}
When you run the application now, there will be no errors.
If you want to learn more about tysringe
go through this link