Dependency-Injection-TS
class App
Dependency Injection Pattern in TypeScript
Kacper Walczak · 05-11-2023
Learn how to implement Dependency Injection in TS.
Dependency Injection Pattern in TypeScript.
Note! Unfortunately it does not work with Bun, but it works with NodeJS.
Usage
Pattern usage:
// Simple class for dependency injection testing
class Test {
constructor() {
console.log("Test constructor - created");
}
doSomething(): void {
console.log("Test did something");
}
}
// Entry point class
@Injectable()
class App implements OnDestroy {
constructor(public test: Test) {}
onDestroy(): void {
console.log("App destroyed");
}
}
const [entryClass, destroy] = bootstrap<App>(App);
// Initialize the entry point class instance
// Output:
// Injector resolving class Test
// Test constructor - created
// Injector created class Test
// Injector resolving class App
// Injector created class App
entryClass.test.doSomething();
// Use the entry point class instance
// Output:
// Test did something
destroy();
// Destroy the entry point class instance
// Output:
// App destroyedPattern
Import reflect-metadata
Import the reflect-metadata package to be able to use Reflect's metadata.
import "reflect-metadata";Type interface
Create type for classes that will be resolved in Injector.
export interface Type<T> {
new (...args: any[]): T;
}Injectable decorator
Decorator function to annotate classes which can inject another ones in constructors.
@Injectable()
class App { /* ... */ }export const Injectable = (): ((target: Type<any>) => void) => {
return (target: Type<any>) => {};
};OnDestroy interface
Lifecycle hook that is used for releasing a resource.
It will be called automatically by DI container.
export interface OnDestroy {
onDestroy(): void;
}Injector class
Injector class that is used for resolving classes.
class App { /* ... */ }
const injector = new Injector();
// bootstrap all dependencies
const entryClass = injector.resolve<App>(App);/**
* Every entry point class instance starts its own dependency container.
* Injector ensures that all decorated classes in the container are singletons.
*/
export class Injector extends Map {
public resolve<T>(target: Type<any>): T {
const tokens = Reflect.getMetadata("design:paramtypes", target) || [];
const injections = tokens.map((token: Type<any>) => {
return this.resolve<any>(token);
});
console.log(`Injector is resolving class ${target.name}`);
const classInstance = this.get(target);
if (classInstance) {
return classInstance;
}
const newClassInstance = new target(...injections);
this.set(target, newClassInstance);
console.log(
`Injector has created class ${newClassInstance.constructor.name}`
);
return newClassInstance;
}
public onDestroy(): void {
for (const value of this.values()) {
if (typeof value["onDestroy"] === "function") {
value["onDestroy"]();
}
}
this.clear();
}
}Bootstrap function
Function that is used for bootstrapping the entry point class instance.
const [entryClass, destroy] = bootstrap<App>(App);/**
* Bootstraps the entry point class instance of type T.
*
* @returns entry point class instance and the "destroy" function which destroy the DI container
*/
export const bootstrap = <T>(target: Type<any>): [T, () => void] => {
// there is exactly one Injector pro entry point class instance
const injector = new Injector();
// bootstrap all dependencies
const entryClass = injector.resolve<T>(target);
return [entryClass, () => injector.onDestroy()];
};That's it! Now you can use this pattern in your applications/libraries.
READ
Latest readings
Readings are sites which will help you with detailed
information about given topic. Read latest ones from Learn.
06-03-2026
Build your own local voice assistant powered by Ollama.
06-03-2026
Generate YouTube thumbnails with FastAPI and Ollama.
05-09-2024
Compare Neo4j and Tigergraph databases, which is easier to work with, etc.