What is Inversion of Control?
Tired of writing code that’s hard to test and even harder to update? Inversion of Control flips the way we think about program structure,testing and more cool stuff that we shall dive into.....
Ever felt like your code is doing too much—invoking too many things, micromanaging every detail?Well... Inversion of Control is the go to shift that lets you delegate responsibilities to the framework, so you may write less glue code and more business logic.
In Software Engineering:
There are these these two very common terms Library and Framework these words may be used interchangeabley but each has a very distinct meaning.
Library - When you use a library you are in control— your code is doing most of the work . At certain times when you need a plugin e.g. like making a network request or parsing some json payload you call the ibrary(axios, JSONObject) when and where you need it.
Thus its more of like a plug and play kind off pattern.Ask for help receive the help reurn back to control flow to the code ..
Framework - Framework - When you use a framework, the framework is in control—it dictates the flow and structure of your application. Instead of you calling the framework when you need it, the framework calls your code when it needs specific functionality. This is the essence of Inversion of Control.
With a framework:
- You fill in the blanks that the framework provides
- The framework manages the application lifecycle (startup, request handling, shutdown)
- Your code plugs into predefined extension points (controllers, services, middleware)
- The framework orchestrates when and how your code gets executed
- You follow the framework's conventions and architectural patterns
Think of it like this: With a library, you're the director calling actors (functions) when needed. With a framework, you're an actor waiting for the director (framework) to call you onto the stage.
Examples:
- NestJS: You define controllers and services, but NestJS decides when to instantiate them and how to handle HTTP requests
- React: You write components, but React controls when to render them and manage the component lifecycle
- Express: You define route handlers, but Express controls when to invoke them based on incoming requests
Definition
Inversion of Control
(IoC) Fancy term right??What is even inversion of control??The question is: “what aspect of control are they inverting?”
One of the the key principles particularly in building robust, complex applications is ensuring a structured dependency management, maintaining clean and manageable codeand reducing tight coupling between components.
Inversion of Control (IoC) is a principle in involves inverting the control flow of the execution of a program by delegating this control to an external framework or a lightweight container.
In a more OOP definition way its a design principle in which the control of object creation and dependency management is inverted.
Instead of the component or class creating its dependencies, an external entity (often a framework or a container) is responsible for creating and injecting these dependencies.
Example:
// ❌ WITHOUT IoC/DI - Class creates its own dependency
export class Storage {
private database: Database;
constructor() {
this.database = new Database('db.sqlite'); // Hard-coded dependency creation
this.database.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
group_id INTEGER,
creation_time TIMESTAMP
)`);
}
//...
}
// ✅ WITH IoC/DI - Dependency is injected from outside arguements are
// passed from outside
export class Storage {
private database: Database;
constructor(database: Database) { // Dependency injected via constructor
this.database = database;
this.database.run(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT,
group_id INTEGER,
creation_time TIMESTAMP
)`);
}
//...
}
Often the term IoC points us to another term named Dependency Injection(DI).We'll cover more about DI in our upcoming series for now lets see how it relates with IoC.
Dependency Injection
Dependency Injection is a design pattern that implements IoC. It allows a class to receive its dependencies from an external source rather than creating them itself.
DI is a technique where an object’s dependencies are provided to it, rather than the object creating them itself.
There are three main styles of dependency injection.that can be done via constructor injection, method injection, or property injection.
How Nestjs Handles IoC
When a class is marked with the @Injectable()
decorator, it becomes a provider that can be injected into other classes. These dependencies are specified in the constructor of a class, and the IoC container resolves them at runtime. This approach promotes loose coupling, making code more modular and easier to manage.
The IoC container in NestJS manages the creation, configuration, and lifecycle of application components, allowing for dependency injection where the container automatically provides dependencies to classes that need them. Developers can declare their dependencies in constructors, and NestJS takes care of the rest.
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
Getting Started
Deep Dive into the three ways of practicaly implemetning Dependency Inection:
We will use Nestjs for the examples:
Constructor Injection
- This is the most popular way to do Dependency Injection.
- Dependencies are injected through the class constructor making dependencies explicit and required.Its actulally the best for mandatory dependencies
- TypeScript automatically handles the injection with
private readonly
// Email service
@Injectable()
export class EmailService {
async sendWelcomeEmail(email: string): Promise<void> {
console.log(`Sending welcome email to ${email}`);
}
}
// Main service using constructor injection
@Injectable()
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly emailService: EmailService,
) {}
Property Injection
- Uses
@Inject()
decorator on class properties.thus dependencies are injected directly into properties. - This technique becomes useful when you need optional dependencies.
// Configuration service most people have this as their config.js/ts // when using nodejs
@Injectable()
export class ConfigService {
get databaseUrl(): string {
return process.env.DATABASE_URL || 'localhost:5432';
}
get jwtSecret(): string {
return process.env.JWT_SECRET || 'default-secret';
}
}
// Service using property injection
@Injectable()
export class PaymentService {
@Inject(ConfigService)
private configService: ConfigService;
Method Injection
- This technique uses
@Inject()
decorator on methods to to inject dependencies through the setter methods - It allows for conditional or lazy dependency injection
- Often Good for optional dependencies that might change during runtime
// Cache service
@Injectable()
export class CacheService {
private cache = new Map<string, any>();
set(key: string, value: any): void {
this.cache.set(key, value);
}
get(key: string): any {
return this.cache.get(key);
}
has(key: string): boolean {
return this.cache.has(key);
}
}
// Analytics service
@Injectable()
export class AnalyticsService {
track(event: string, data: any): void {
console.log(`Tracking event: ${event}`, data);
}
}
// Service using method injection
@Injectable()
export class ProductService {
private cacheService: CacheService;
private analyticsService: AnalyticsService;
// Method injection via setter methods the method injection
// happens here
@Inject(CacheService)
setCacheService(cacheService: CacheService): void {
this.cacheService = cacheService;
}
@Inject(AnalyticsService)
setAnalyticsService(analyticsService: AnalyticsService): void {
this.analyticsService = analyticsService;
}
Conclusion
The beauty of Inversion of Control lies in its simplicity: stop micromanaging your dependencies and let the framework handle the heavy lifting. Your business logic becomes cleaner, your tests become simpler, and your code becomes more professional....
As you continue building with NestJS, remember that DI(Implementation of IoC) isn't just about injecting services, it's about inverting the responsibility of object creation and lifecycle management. This shift from "I'll create what I need" to "I'll receive what I need" is what separates junior developers from senior architects.
Happy Coding :-) ....