Dependency Injection Basics in Angular
The Restaurant Kitchen Analogy 🍳
Imagine you own a restaurant. Your chefs need ingredients to cook meals. Now, there are two ways to get ingredients:
The Hard Way: Each chef goes to the market, picks ingredients, carries them back, and manages everything themselves. Exhausting, right?
The Smart Way: You hire a delivery service. Chefs just say what they need, and ingredients magically appear! The delivery service handles everything.
Dependency Injection (DI) is like that delivery service. Your Angular components just ask for what they need, and Angular delivers it. No hunting. No hassle.
What is Dependency Injection?
Simple Definition: Dependency Injection is a way for your code to ask for things it needs, instead of creating them itself.
Think of it like this:
- Without DI: “I’ll build my own bicycle from scratch every time I need to ride.”
- With DI: “Someone hands me a bicycle when I need it.”
Why Does This Matter?
| Without DI | With DI |
|---|---|
| Components create their own helpers | Angular creates helpers and delivers them |
| Hard to test | Easy to test |
| Changes ripple everywhere | Changes stay contained |
| Tight coupling | Loose coupling |
Real Example
Without DI (The Hard Way):
class Chef {
private ingredient = new Tomato();
// Chef creates its own tomato
// What if we want onion instead?
// We must change the Chef class!
}
With DI (The Smart Way):
class Chef {
constructor(
private ingredient: Vegetable
) {}
// Angular delivers whatever
// vegetable we configure!
}
The Injectable Decorator
What is @Injectable()?
The @Injectable() decorator is like a name tag at a party. It tells Angular: “Hey! I’m available to be delivered to anyone who needs me!”
Without this name tag, Angular wouldn’t know this class exists for delivery.
The Syntax
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class DeliveryService {
deliverFood() {
return 'Pizza is here! 🍕';
}
}
Breaking It Down
graph TD A["@Injectable#40;#41;"] --> B["Tells Angular: I can be injected"] B --> C["providedIn: 'root'"] C --> D["Available everywhere in the app"]
Key Points:
@Injectable()= “I’m available for delivery”- Without it = Angular ignores the class
- It’s a decorator (special function that adds powers)
providedIn Options
Remember our delivery service? Now let’s decide where the delivery truck can go!
Option 1: ‘root’ (Everywhere!)
@Injectable({
providedIn: 'root'
})
export class GlobalService { }
What it means: This service is available everywhere in your app. Like a pizza chain with delivery to every neighborhood!
Use when: You want the same service instance across your entire application.
Option 2: ‘platform’ (Multiple Apps)
@Injectable({
providedIn: 'platform'
})
export class SharedService { }
What it means: Shared across multiple Angular apps on the same page. Rare, but useful!
Option 3: ‘any’ (Fresh Each Time)
@Injectable({
providedIn: 'any'
})
export class FreshService { }
What it means: Each lazy-loaded module gets its own fresh copy. Like each restaurant location making their own fresh dough!
Option 4: A Specific Module
@Injectable({
providedIn: OrdersModule
})
export class OrderService { }
What it means: Only available in that specific module. Like a local bakery that only delivers to one neighborhood.
Quick Reference
| providedIn | Scope | Use Case |
|---|---|---|
'root' |
Whole app | Most services |
'platform' |
Multiple apps | Micro-frontends |
'any' |
Per lazy module | Module-specific state |
SomeModule |
That module only | Feature-specific |
Services: Your Helpful Assistants
What is a Service?
A service is a class that does a specific job. Think of services like specialists in a hospital:
- Doctor → handles diagnoses
- Nurse → handles patient care
- Pharmacist → handles medications
In Angular:
- AuthService → handles login/logout
- DataService → handles API calls
- LoggerService → handles logging
Creating a Service
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CalculatorService {
add(a: number, b: number): number {
return a + b;
}
subtract(a: number, b: number): number {
return a - b;
}
}
Using a Service in a Component
import { Component } from '@angular/core';
import { CalculatorService } from './calculator.service';
@Component({
selector: 'app-math',
template: `<p>5 + 3 = {{ result }}</p>`
})
export class MathComponent {
result: number;
constructor(private calc: CalculatorService) {
this.result = this.calc.add(5, 3);
}
}
The Magic Moment
Notice we didn’t write:
private calc = new CalculatorService();
We just asked for it in the constructor, and Angular delivered it! That’s DI in action!
graph TD A["MathComponent needs CalculatorService"] --> B["Angular checks: Is CalculatorService injectable?"] B --> C["Yes! @Injectable decorator found"] C --> D["Angular creates or finds instance"] D --> E["Delivers to MathComponent constructor"]
Singleton Services: One for All
The Problem
Imagine if every time someone ordered coffee at a cafe, they got a brand new coffee machine. Wasteful, right?
The Solution: Singletons
A singleton is a single shared instance. One coffee machine serves everyone!
How Angular Does It
When you use providedIn: 'root':
@Injectable({
providedIn: 'root' // 👈 Makes it singleton!
})
export class CartService {
items: string[] = [];
addItem(item: string) {
this.items.push(item);
}
}
Every component gets the SAME CartService!
Seeing It in Action
// Component A adds an item
this.cartService.addItem('Apple');
// Component B sees the same cart
console.log(this.cartService.items);
// Output: ['Apple']
// 👆 Same instance, shared data!
Why Singletons Rock
| Benefit | Explanation |
|---|---|
| Shared State | All components see the same data |
| Memory Efficient | One instance, not hundreds |
| Consistent | No confusion about which instance has what |
Non-Singleton Example
If you want a fresh instance per component:
@Component({
selector: 'app-special',
providers: [FreshService] // 👈 New instance!
})
export class SpecialComponent { }
This creates a new FreshService just for SpecialComponent and its children.
Putting It All Together
Let’s build a complete example!
Step 1: Create the Service
// user.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class UserService {
private currentUser = 'Guest';
login(name: string) {
this.currentUser = name;
}
getUser(): string {
return this.currentUser;
}
}
Step 2: Use in Components
// header.component.ts
@Component({
selector: 'app-header',
template: `<h1>Hello, {{ userName }}</h1>`
})
export class HeaderComponent {
userName: string;
constructor(private userService: UserService) {
this.userName = userService.getUser();
}
}
// login.component.ts
@Component({
selector: 'app-login',
template: `
<button (click)="login()">
Login as Alice
</button>
`
})
export class LoginComponent {
constructor(private userService: UserService) {}
login() {
this.userService.login('Alice');
}
}
The Flow
graph TD A["UserService created once"] --> B["HeaderComponent asks for it"] A --> C["LoginComponent asks for it"] B --> D["Both get SAME instance"] C --> D D --> E["Shared user state!"]
Key Takeaways 🎯
- Dependency Injection = Angular delivers what your code needs
- @Injectable() = “I’m available for delivery” decorator
- providedIn: ‘root’ = Available everywhere as singleton
- Services = Specialist classes that do specific jobs
- Singletons = One shared instance for everyone
The Golden Rule
Don’t create dependencies yourself. Let Angular deliver them!
Before DI:
private service = new MyService(); // ❌ Hard way
With DI:
constructor(private service: MyService) {} // ✅ Smart way
You Did It! 🎉
You now understand how Angular’s Dependency Injection works! You’re like a restaurant owner who just discovered delivery services — your kitchen (code) will never be the same!
Remember: Your components are the chefs. Services are the ingredients. Angular’s DI is the delivery truck. Now go build something delicious! 🚀
