﻿Topic 7:  IoC Containers & Advanced Dependency Management

## 1. Problem Statement

## **Case Study: Scaling the News Aggregator**

Your news aggregator platform is growing:

-   You now fetch from dozens of sources, each with its own configuration.
    
-   You want to inject not just one, but many dependencies (e.g., logger, cache, analytics).
    
-   Manually wiring up dependencies everywhere is becoming error-prone and hard to maintain.
<img src="https://i.postimg.cc/kMBkmJhR/news-agg.png" width="400px" />    

**The problem:**  
How can you automate the creation and injection of dependencies, so your system is scalable, maintainable, and easy to test?

## 2. Learning Objectives

By the end of this tutorial, you will:
-   Understand what an IoC (Inversion of Control) container is.
    
-   Use a TypeScript IoC container (TypeDI) to manage dependencies.
    
-   Register and resolve dependencies automatically.
    
-   See how IoC containers simplify large-scale application development.
    

## 3. Concept Introduction with Analogy

## **Analogy: Hotel Concierge Service**

Imagine a hotel guest (your class) needs various services: room cleaning, food delivery, taxi booking.

-   Instead of contacting each service directly, the guest calls the  **concierge**  (IoC container), who arranges everything behind the scenes.
    
-   The guest doesn’t care who provides the service, just that it’s delivered on request.
    

**An IoC container is your concierge:**  
It manages all services (dependencies) and delivers them to your classes as needed.
## **What Is Inversion of Control (IoC)?**

**Inversion of Control**  is a principle where the flow of a program’s control is inverted:

-   Instead of your classes creating and managing their dependencies,
    
-   An external system (the IoC container) creates and supplies those dependencies.
    

**Why use IoC?**

-   This decouples your classes from specific implementations, making your code more flexible, testable, and maintainable.

## **What is an IoC Container?**

-   An  **IoC Container**  is a framework that manages the creation, configuration, and injection of dependencies automatically.
    
-   Instead of manually creating dependencies, you  **register**  them with the container and  **request**  them when needed.
  

## 5. Step-by-Step Data Modeling & Code Walkthrough

 **Step 1: Install  TypeDI****


 ```bash
npm install typedi reflect-metadata
 ```
-   In your  `tsconfig.json`, enable decorators:
    
   ```typescript
    "experimentalDecorators":  true, 
    "emitDecoratorMetadata":  true
 ```

- At the very top of your entry file (e.g., `index.ts`):

 ```typescript
import "reflect-metadata";
  ```
**Step 2: Define Interfaces and Implementations**

 ```typescript
// src/NewsSource.ts
export interface NewsSource {
  fetchArticles(): Promise<string[]>;
}
 ```

 ```typescript
// src/RSSFeedSource.ts
import { Service } from "typedi";
import { NewsSource } from "./NewsSource";

@Service()
export class RSSFeedSource implements NewsSource {
  async fetchArticles(): Promise<string[]> {
    return ["RSS: Article 1", "RSS: Article 2"];
  }
}

// src/APISource.ts
import { Service } from "typedi";
import { NewsSource } from "./NewsSource";

@Service()
export class APISource implements NewsSource {
  async fetchArticles(): Promise<string[]> {
    return ["API: Article A", "API: Article B"];
  }
}

 ```
 -   `@Service()`  registers the class with TypeDI’s container.
    
  **Step 3: Inject Dependencies Automatically**

 ```typescript
// src/NewsAggregator.ts
import { Service, Inject } from "typedi";
import { NewsSource } from "./NewsSource";

@Service()
export class NewsAggregator {
  constructor(
    @Inject(() => RSSFeedSource) private source: NewsSource
  ) {}

  async getLatestArticles() {
    const articles = await this.source.fetchArticles();
    articles.forEach(article => console.log(article));
  }
}
 ```

-   `@Inject(() => RSSFeedSource)`  tells TypeDI which implementation to inject.

 **Step 4: Resolve and Use**
 ```typescript
// src/index.ts
import "reflect-metadata";
import { Container } from "typedi";
import { NewsAggregator } from "./NewsAggregator";

const aggregator = Container.get(NewsAggregator);
aggregator.getLatestArticles(); // Uses RSSFeedSource by default
 ```
-   `Container.get()`  creates the class and injects all dependencies. 

 **Step 5: Swapping Implementations**
 ```typescript
import { Container } from "typedi";
import { NewsAggregator } from "./NewsAggregator";
import { APISource } from "./APISource";
import { NewsSource } from "./NewsSource";

// Override the NewsSource dependency
Container.set(NewsSource, new APISource());

const aggregator2 = Container.get(NewsAggregator);
aggregator2.getLatestArticles(); // Now uses APISource

 ```
-   No changes to  `NewsAggregator`  code needed!
    
 **Step 6: Swapping Implementations**

Suppose you want to use  `APISource`  instead of  `RSSFeedSource`:

 ```typescript
container.rebind<NewsSource>(TYPES.NewsSource).to(APISource);

const aggregator2 = container.resolve(NewsAggregator);
aggregator2.getLatestArticles(); // Now uses APISource
 ```
 -   No changes to  `NewsAggregator`  code needed!
 
**Step 7: How Does TypeDI Know What to Inject?**

-   **Decorators:**
    
    -   `@Service()`  marks a class as injectable.
        
    -   `@Inject()`  specifies which dependency to inject.
        
-   **Metadata:**
    
    -   TypeDI uses TypeScript’s  `reflect-metadata`  to read type information and decorator hints.

## 6.  Challenge 

1.  **Register a new  `APISource`  with the container.**
    
2.  **Swap the implementation**  from  `RSSFeedSource`  to  `APISource`  without changing the  `NewsAggregator`  code.
    
3.  **Write a test**  that injects a mock source to verify the aggregator’s behavior.

## 7. Quick Recap & Key Takeaways

-   **IoC Containers**  automate dependency management.
    
-   Register, configure, and swap dependencies centrally.
    
-   Classes remain clean, focused, and testable.
    

## 8. (Optional) Programmer’s Workflow Checklist

-   Define interfaces for dependencies.
    
-   Register implementations with the container.
    
-   Use decorators (`@injectable`,  `@inject`) for automatic injection.
    
-   Swap implementations for testing or scaling.
    

