Fundamentals 16 min read

Implementing IoC and DI in TypeScript: A Practical Guide

This guide shows how to build a lightweight Inversion of Control container in TypeScript and use Reflect‑Metadata decorators to register providers and inject dependencies, demonstrating how constructor‑based injection decouples classes, simplifies testing, and mirrors patterns used in frameworks such as NestJS, Midway, and Angular.

NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
Implementing IoC and DI in TypeScript: A Practical Guide

This article explains how to implement Inversion of Control (IoC) and Dependency Injection (DI) in TypeScript, inspired by frameworks like NestJS and Midway.

1. Introduction to IoC

IoC (Inversion of Control) is a design principle in object-oriented programming that reduces coupling between components. Instead of classes creating their dependencies internally, a container manages dependency creation and lookup, transferring control from the class to the container.

2. Understanding Coupling

The article demonstrates the problem with tight coupling through a simple example: when class A depends on class B, and B requires a parameter p in its constructor, any change to B's initialization forces modifications to A and potentially all classes in the dependency chain. This creates a maintenance nightmare in complex applications.

3. Decoupling Solution

The solution is to have classes receive their dependencies through constructor parameters rather than creating them internally. This allows modifying B's initialization without affecting A.

4. The Container

A simple IoC container can be implemented using a Map to store class definitions and their initialization parameters:

export class Container {
    bindMap = new Map();
    bind(identifier: string, clazz: any, constructorArgs: Array<any>) {
        this.bindMap.set(identifier, { clazz, constructorArgs });
    }
    get<T>(identifier: string): T {
        const target = this.bindMap.get(identifier);
        const { clazz, constructorArgs } = target;
        const inst = Reflect.construct(clazz, constructorArgs);
    }
}

5. Dependency Injection (DI)

DI is an implementation technique for IoC. The article explains how to solve two main problems: automatic class registration at startup, and automatic dependency injection during instantiation.

6. Using Reflect Metadata

TypeScript's Reflect Metadata enables decorator-based metadata storage. The article shows how to install and configure it:

npm i reflect-metadata --save

With emitDecoratorMetadata set to true in tsconfig.json, decorators can define and read metadata from classes.

7. Provider Decorator

The Provider decorator marks classes for IoC container registration:

export function Provider(identifier: string, args?: Array<any>) {
    return function (target: any) {
        Reflect.defineMetadata(CLASS_KEY, { id: identifier, args: args || [] }, target);
        return target;
    };
}

8. Inject Decorator

The Inject decorator marks properties that need dependency injection:

export function Inject() {
    return function (target: any, targetKey: string) {
        const annotationTarget = target.constructor;
        let props = {};
        if (Reflect.hasOwnMetadata(PROPS_KEY, annotationTarget)) {
            props = Reflect.getMetadata(PROPS_KEY, annotationTarget);
        }
        props[targetKey] = { value: targetKey };
        Reflect.defineMetadata(PROPS_KEY, props, annotationTarget);
    };
}

9. Final Usage

With these decorators, the final code becomes clean and decoupled:

@Provider('b', [10])
class B {
    constructor(p: number) { this.p = p; }
}

@Provider('a')
class A {
    @Inject()
    private b: B;
}

const container = new Container();
load(container);
console.log(container.get('a'));

Summary

IoC and DI provide significant benefits including decoupling, easier unit testing, and better dependency analysis. While originally a server-side concept, IoC has become important in frontend development as well, with frameworks like AngularJS implementing their own IoC containers.

Design Patternssoftware architectureTypeScriptIoCdependency injectiondecoratorInversion of Controlreflect-metadata
NetEase Cloud Music Tech Team
Written by

NetEase Cloud Music Tech Team

Official account of NetEase Cloud Music Tech Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.