Dependency Injection: Provider Configuration
The Restaurant Kitchen Analogy
Imagine you own a restaurant. Every day, your chefs need ingredients to cook meals. But here’s the question: Where do the ingredients come from?
You could:
- Hire a specific supplier (useClass)
- Use pre-made ingredients from your pantry (useValue)
- Have a special recipe to create ingredients on-demand (useFactory)
- Create a secret code name for special items (InjectionToken)
This is exactly what Provider Configuration does in Angular! It tells Angular: “When someone asks for THIS thing, give them THAT thing.”
What is a Provider?
A provider is like a recipe card that tells Angular:
“When someone needs
IngredientService, here’s how to get it!”
graph TD A["Component asks for Service"] --> B{Angular checks Providers} B --> C["Provider says how to create it"] C --> D["Service is delivered!"]
Think of providers as instructions for Angular’s delivery system.
useClass Provider
The Story
Your restaurant needs a Chef. You write a job posting:
“I need a Chef. Hire someone from the
ProfessionalChefclass.”
That’s useClass! You’re saying: “When someone asks for Chef, create a new ProfessionalChef.”
Simple Example
// The service we want
@Injectable()
class LoggerService {
log(msg: string) {
console.log(msg);
}
}
// Provider configuration
providers: [
{
provide: LoggerService,
useClass: LoggerService
}
]
Why Use It?
Swap implementations easily!
// Development: use a noisy logger
// Production: use a quiet logger
providers: [
{
provide: LoggerService,
useClass: environment.production
? QuietLogger
: NoisyLogger
}
]
Real-Life Scenario
Imagine you have a DataService. In testing, you want a MockDataService:
// Normal app
providers: [
{ provide: DataService, useClass: DataService }
]
// Testing
providers: [
{ provide: DataService, useClass: MockDataService }
]
Your component asks for DataService. Angular gives it MockDataService. Magic!
useValue Provider
The Story
Instead of hiring a chef, you have a pre-made sandwich sitting in the fridge. When someone asks for lunch, you just hand them the sandwich.
No cooking. No waiting. Just grab and give.
Simple Example
// A simple configuration object
const APP_CONFIG = {
apiUrl: 'https://api.example.com',
maxRetries: 3,
timeout: 5000
};
// Provider: just use this value!
providers: [
{
provide: 'APP_CONFIG',
useValue: APP_CONFIG
}
]
When to Use It?
Perfect for:
- Configuration objects
- Constants
- Simple values that don’t need to be “created”
Real-Life Scenario
// API settings
const API_SETTINGS = {
baseUrl: 'https://myapp.com/api',
version: 'v2'
};
// Provide it
providers: [
{ provide: 'API_SETTINGS', useValue: API_SETTINGS }
]
// Use it in a service
constructor(
@Inject('API_SETTINGS') private settings: any
) {
console.log(this.settings.baseUrl);
// Output: https://myapp.com/api
}
useFactory Provider
The Story
You don’t have a fixed chef or a pre-made sandwich. Instead, you have a recipe that decides what to make based on the situation.
Is it morning? Make breakfast. Is it evening? Make dinner.
useFactory is your decision-making recipe!
Simple Example
// Factory function - makes decisions!
function loggerFactory(isDev: boolean) {
if (isDev) {
return new VerboseLogger();
}
return new SimpleLogger();
}
// Provider with factory
providers: [
{
provide: LoggerService,
useFactory: loggerFactory,
deps: ['IS_DEV_MODE']
}
]
The Power of Factories
Factories can:
- Make decisions based on conditions
- Depend on other services
- Run async operations (with a bit more setup)
Real-Life Scenario
// Factory that needs another service
function dataServiceFactory(
http: HttpClient,
config: ConfigService
) {
const baseUrl = config.getApiUrl();
return new DataService(http, baseUrl);
}
// Provider
providers: [
{
provide: DataService,
useFactory: dataServiceFactory,
deps: [HttpClient, ConfigService]
}
]
Understanding deps
The deps array tells Angular: “Before running the factory, get me THESE things.”
graph TD A["Angular sees Factory Provider"] --> B["Reads deps array"] B --> C["Gets HttpClient"] B --> D["Gets ConfigService"] C --> E["Calls factory with dependencies"] D --> E E --> F["Factory returns DataService"]
InjectionToken
The Story
In your restaurant, you have special items without official names. Maybe it’s grandma’s secret sauce or the special of the day.
You create a secret code: “ITEM-47” means grandma’s sauce.
InjectionToken is that secret code for Angular!
The Problem It Solves
// This causes problems! Strings can collide.
providers: [
{ provide: 'config', useValue: {...} }
]
// Another library also uses 'config'!
// CONFLICT!
The Solution: InjectionToken
// Create a unique token
export const APP_CONFIG = new InjectionToken<AppConfig>(
'app.config' // Description for debugging
);
// Use the token
providers: [
{
provide: APP_CONFIG,
useValue: { apiUrl: 'https://...' }
}
]
// Inject using the token
constructor(
@Inject(APP_CONFIG) private config: AppConfig
) { }
Why Is It Unique?
Each InjectionToken is a unique object in memory. Even if two tokens have the same description, they’re different!
const TOKEN_A = new InjectionToken('config');
const TOKEN_B = new InjectionToken('config');
// TOKEN_A !== TOKEN_B (they're different!)
Real-Life Scenario
// tokens.ts
export const API_URL = new InjectionToken<string>('api.url');
export const MAX_RETRIES = new InjectionToken<number>('max.retries');
// app.module.ts
providers: [
{ provide: API_URL, useValue: 'https://api.myapp.com' },
{ provide: MAX_RETRIES, useValue: 3 }
]
// my.service.ts
constructor(
@Inject(API_URL) private apiUrl: string,
@Inject(MAX_RETRIES) private maxRetries: number
) { }
All Four Together: A Complete Picture
Let’s see all four in action at our restaurant:
// 1. InjectionToken - Create secret codes
export const KITCHEN_CONFIG = new InjectionToken<KitchenConfig>(
'kitchen.config'
);
// 2. useValue - The pre-made pantry items
const kitchenSettings = {
maxDishes: 100,
style: 'Italian'
};
// 3. useClass - Hire the chef class
@Injectable()
class ItalianChef implements Chef { }
// 4. useFactory - Decision-making recipe
function menuFactory(config: KitchenConfig) {
if (config.style === 'Italian') {
return new ItalianMenu();
}
return new GenericMenu();
}
// All providers together
providers: [
// useValue
{ provide: KITCHEN_CONFIG, useValue: kitchenSettings },
// useClass
{ provide: Chef, useClass: ItalianChef },
// useFactory with deps
{
provide: Menu,
useFactory: menuFactory,
deps: [KITCHEN_CONFIG]
}
]
Quick Decision Guide
| I need to… | Use this |
|---|---|
| Create instance of a class | useClass |
| Use a ready-made value/object | useValue |
| Make runtime decisions | useFactory |
| Avoid string collisions | InjectionToken |
Common Patterns
Pattern 1: Environment-Based Switching
providers: [
{
provide: ApiService,
useClass: environment.production
? ProductionApi
: MockApi
}
]
Pattern 2: Configuration Token
// Define token with default value
export const TIMEOUT = new InjectionToken<number>(
'timeout',
{ factory: () => 3000 } // Default!
);
Pattern 3: Factory with Multiple Deps
{
provide: ComplexService,
useFactory: (a, b, c) => new ComplexService(a, b, c),
deps: [ServiceA, ServiceB, ServiceC]
}
You Did It!
You now understand the four ways to configure providers in Angular:
- useClass - “Create an instance of THIS class”
- useValue - “Use THIS exact value”
- useFactory - “Run THIS function to decide”
- InjectionToken - “Here’s a unique name tag”
Think of Angular’s DI as a smart delivery system. Providers are the delivery instructions. And you’re now the master delivery planner!
graph TD A["🏠 Component needs something"] --> B{📋 Check Provider} B -->|useClass| C["🏭 Create from Class"] B -->|useValue| D["📦 Grab the Value"] B -->|useFactory| E["🔧 Run Factory Function"] C --> F["✅ Delivered!"] D --> F E --> F
Go build something amazing!
