0
# Conditional Resolution
1
2
InversifyJS supports sophisticated conditional binding resolution, allowing you to bind multiple implementations to the same service identifier and resolve them based on context, names, tags, or custom conditions. This enables flexible dependency injection scenarios and context-aware service selection.
3
4
## Conditional Binding Syntax
5
6
### BindWhenFluentSyntax
7
8
The primary interface for configuring conditional resolution rules.
9
10
```typescript { .api }
11
interface BindWhenFluentSyntax<T> {
12
when(constraint: (request: ResolutionContext) => boolean): BindOnFluentSyntax<T>;
13
whenTargetNamed(name: string): BindOnFluentSyntax<T>;
14
whenTargetTagged(tag: string, value: any): BindOnFluentSyntax<T>;
15
whenInjectedInto(parent: Newable<any> | string): BindOnFluentSyntax<T>;
16
whenParentNamed(name: string): BindOnFluentSyntax<T>;
17
whenParentTagged(tag: string, value: any): BindOnFluentSyntax<T>;
18
whenAnyAncestorIs(ancestor: Newable<any> | string): BindOnFluentSyntax<T>;
19
whenNoAncestorIs(ancestor: Newable<any> | string): BindOnFluentSyntax<T>;
20
whenAnyAncestorNamed(name: string): BindOnFluentSyntax<T>;
21
whenNoAncestorNamed(name: string): BindOnFluentSyntax<T>;
22
whenAnyAncestorTagged(tag: string, value: any): BindOnFluentSyntax<T>;
23
whenNoAncestorTagged(tag: string, value: any): BindOnFluentSyntax<T>;
24
whenAnyAncestorMatches(constraint: (request: ResolutionContext) => boolean): BindOnFluentSyntax<T>;
25
whenNoAncestorMatches(constraint: (request: ResolutionContext) => boolean): BindOnFluentSyntax<T>;
26
}
27
```
28
29
## Named Bindings
30
31
Use named bindings to distinguish between multiple implementations of the same interface.
32
33
### Target Named Binding
34
35
```typescript
36
interface ILogger {
37
log(message: string): void;
38
}
39
40
@injectable()
41
class ConsoleLogger implements ILogger {
42
log(message: string) {
43
console.log(`[CONSOLE] ${message}`);
44
}
45
}
46
47
@injectable()
48
class FileLogger implements ILogger {
49
log(message: string) {
50
// Write to file
51
console.log(`[FILE] ${message}`);
52
}
53
}
54
55
// Bind with names
56
container.bind<ILogger>("Logger").to(ConsoleLogger).whenTargetNamed("console");
57
container.bind<ILogger>("Logger").to(FileLogger).whenTargetNamed("file");
58
59
// Usage with @named decorator
60
@injectable()
61
class UserService {
62
constructor(
63
@inject("Logger") @named("console") private consoleLogger: ILogger,
64
@inject("Logger") @named("file") private fileLogger: ILogger
65
) {}
66
67
processUser(user: User) {
68
this.consoleLogger.log("Processing user...");
69
this.fileLogger.log(`User processed: ${user.id}`);
70
}
71
}
72
```
73
74
### Parent Named Binding
75
76
Resolve based on the name of the parent service.
77
78
```typescript
79
@injectable()
80
class DatabaseLogger implements ILogger {
81
log(message: string) {
82
// Log to database
83
}
84
}
85
86
@injectable()
87
class WebLogger implements ILogger {
88
log(message: string) {
89
// Log to web service
90
}
91
}
92
93
// Bind based on parent names
94
container.bind<ILogger>("Logger").to(DatabaseLogger).whenParentNamed("database");
95
container.bind<ILogger>("Logger").to(WebLogger).whenParentNamed("web");
96
97
// Parent services with names
98
container.bind<IDatabaseService>("DatabaseService").to(DatabaseService).whenTargetNamed("database");
99
container.bind<IWebService>("WebService").to(WebService).whenTargetNamed("web");
100
```
101
102
## Tagged Bindings
103
104
Use tagged bindings for more complex conditional resolution based on key-value metadata.
105
106
### Target Tagged Binding
107
108
```typescript
109
interface IPaymentProcessor {
110
process(amount: number): Promise<PaymentResult>;
111
}
112
113
@injectable()
114
class CreditCardProcessor implements IPaymentProcessor {
115
async process(amount: number) {
116
// Credit card processing logic
117
return { success: true, transactionId: "cc_123" };
118
}
119
}
120
121
@injectable()
122
class PayPalProcessor implements IPaymentProcessor {
123
async process(amount: number) {
124
// PayPal processing logic
125
return { success: true, transactionId: "pp_456" };
126
}
127
}
128
129
@injectable()
130
class CryptoProcessor implements IPaymentProcessor {
131
async process(amount: number) {
132
// Cryptocurrency processing logic
133
return { success: true, transactionId: "crypto_789" };
134
}
135
}
136
137
// Bind with tags
138
container.bind<IPaymentProcessor>("PaymentProcessor")
139
.to(CreditCardProcessor)
140
.whenTargetTagged("method", "creditcard");
141
142
container.bind<IPaymentProcessor>("PaymentProcessor")
143
.to(PayPalProcessor)
144
.whenTargetTagged("method", "paypal");
145
146
container.bind<IPaymentProcessor>("PaymentProcessor")
147
.to(CryptoProcessor)
148
.whenTargetTagged("method", "crypto");
149
150
// Usage with @tagged decorator
151
@injectable()
152
class CheckoutService {
153
constructor(
154
@inject("PaymentProcessor") @tagged("method", "creditcard")
155
private creditCardProcessor: IPaymentProcessor,
156
157
@inject("PaymentProcessor") @tagged("method", "paypal")
158
private paypalProcessor: IPaymentProcessor,
159
160
@inject("PaymentProcessor") @tagged("method", "crypto")
161
private cryptoProcessor: IPaymentProcessor
162
) {}
163
164
async processPayment(method: string, amount: number) {
165
switch (method) {
166
case "creditcard":
167
return this.creditCardProcessor.process(amount);
168
case "paypal":
169
return this.paypalProcessor.process(amount);
170
case "crypto":
171
return this.cryptoProcessor.process(amount);
172
default:
173
throw new Error(`Unsupported payment method: ${method}`);
174
}
175
}
176
}
177
```
178
179
### Multiple Tag Conditions
180
181
```typescript
182
// Bind with multiple tag conditions
183
container.bind<ILogger>("Logger")
184
.to(DatabaseLogger)
185
.whenTargetTagged("destination", "database")
186
.whenTargetTagged("level", "error");
187
188
container.bind<ILogger>("Logger")
189
.to(ConsoleLogger)
190
.whenTargetTagged("destination", "console")
191
.whenTargetTagged("level", "debug");
192
193
// Usage
194
@injectable()
195
class ErrorHandler {
196
constructor(
197
@inject("Logger")
198
@tagged("destination", "database")
199
@tagged("level", "error")
200
private errorLogger: ILogger
201
) {}
202
}
203
```
204
205
## Injection Context Binding
206
207
### Injected Into Binding
208
209
Resolve based on the class receiving the injection.
210
211
```typescript
212
interface IRepository<T> {
213
save(entity: T): Promise<void>;
214
findById(id: string): Promise<T | null>;
215
}
216
217
@injectable()
218
class UserRepository implements IRepository<User> {
219
async save(user: User) { /* User-specific logic */ }
220
async findById(id: string) { /* User-specific logic */ return null; }
221
}
222
223
@injectable()
224
class ProductRepository implements IRepository<Product> {
225
async save(product: Product) { /* Product-specific logic */ }
226
async findById(id: string) { /* Product-specific logic */ return null; }
227
}
228
229
// Bind based on injection target
230
container.bind<IRepository<any>>("Repository")
231
.to(UserRepository)
232
.whenInjectedInto(UserService);
233
234
container.bind<IRepository<any>>("Repository")
235
.to(ProductRepository)
236
.whenInjectedInto(ProductService);
237
238
@injectable()
239
class UserService {
240
constructor(
241
@inject("Repository") private userRepository: IRepository<User>
242
) {}
243
}
244
245
@injectable()
246
class ProductService {
247
constructor(
248
@inject("Repository") private productRepository: IRepository<Product>
249
) {}
250
}
251
```
252
253
## Ancestor-Based Binding
254
255
### Any Ancestor Conditions
256
257
```typescript
258
interface IConfigProvider {
259
getConfig(): any;
260
}
261
262
@injectable()
263
class TestConfigProvider implements IConfigProvider {
264
getConfig() {
265
return { env: "test", debug: true };
266
}
267
}
268
269
@injectable()
270
class ProdConfigProvider implements IConfigProvider {
271
getConfig() {
272
return { env: "production", debug: false };
273
}
274
}
275
276
// Bind based on ancestor class
277
container.bind<IConfigProvider>("Config")
278
.to(TestConfigProvider)
279
.whenAnyAncestorIs(TestService);
280
281
container.bind<IConfigProvider>("Config")
282
.to(ProdConfigProvider)
283
.whenNoAncestorIs(TestService);
284
285
@injectable()
286
class TestService {
287
constructor(@inject("Database") private db: IDatabase) {}
288
}
289
290
@injectable()
291
class DatabaseService {
292
constructor(@inject("Config") private config: IConfigProvider) {}
293
// Will get TestConfigProvider when created by TestService
294
// Will get ProdConfigProvider otherwise
295
}
296
```
297
298
### Ancestor Named/Tagged Conditions
299
300
```typescript
301
// Bind based on ancestor naming
302
container.bind<ILogger>("Logger")
303
.to(VerboseLogger)
304
.whenAnyAncestorNamed("debug");
305
306
container.bind<ILogger>("Logger")
307
.to(QuietLogger)
308
.whenNoAncestorNamed("debug");
309
310
// Bind based on ancestor tagging
311
container.bind<ICache>("Cache")
312
.to(RedisCache)
313
.whenAnyAncestorTagged("performance", "high");
314
315
container.bind<ICache>("Cache")
316
.to(MemoryCache)
317
.whenNoAncestorTagged("performance", "high");
318
```
319
320
## Custom Conditional Logic
321
322
### Custom Constraint Functions
323
324
```typescript
325
// Environment-based binding
326
container.bind<IEmailService>("EmailService")
327
.to(SmtpEmailService)
328
.when((request) => {
329
return process.env.NODE_ENV === "production";
330
});
331
332
container.bind<IEmailService>("EmailService")
333
.to(MockEmailService)
334
.when((request) => {
335
return process.env.NODE_ENV !== "production";
336
});
337
338
// Time-based binding
339
container.bind<IGreetingService>("GreetingService")
340
.to(MorningGreetingService)
341
.when(() => {
342
const hour = new Date().getHours();
343
return hour >= 6 && hour < 12;
344
});
345
346
container.bind<IGreetingService>("GreetingService")
347
.to(EveningGreetingService)
348
.when(() => {
349
const hour = new Date().getHours();
350
return hour >= 18 || hour < 6;
351
});
352
353
// Request context-based binding
354
container.bind<IAuthService>("AuthService")
355
.to(AdminAuthService)
356
.when((request) => {
357
// Check if any ancestor service has admin role
358
let current = request.currentRequest;
359
while (current) {
360
if (current.target?.hasTag("role", "admin")) {
361
return true;
362
}
363
current = current.parentRequest;
364
}
365
return false;
366
});
367
```
368
369
## Resolution Context
370
371
### ResolutionContext Interface
372
373
```typescript { .api }
374
interface ResolutionContext {
375
container: Container;
376
currentRequest: Request;
377
plan: Plan;
378
addPlan(plan: Plan): void;
379
}
380
381
interface Request {
382
serviceIdentifier: ServiceIdentifier;
383
parentRequest: Request | null;
384
target: Target | null;
385
childRequests: Request[];
386
bindings: Binding<any>[];
387
requestScope: Map<any, any> | null;
388
addChildRequest(serviceIdentifier: ServiceIdentifier, bindings: Binding<any>[], target: Target): Request;
389
}
390
391
interface Target {
392
serviceIdentifier: ServiceIdentifier;
393
name: TaggedType<string>;
394
tags: TaggedType<any>[];
395
hasTag(key: string, value?: any): boolean;
396
isArray(): boolean;
397
matchesArray(name: string): boolean;
398
matchesNamedConstraint(constraint: string): boolean;
399
matchesTaggedConstraint(key: string, value: any): boolean;
400
}
401
```
402
403
## Complex Conditional Examples
404
405
### Multi-Tenant Application
406
407
```typescript
408
interface ITenantService {
409
getTenantData(): any;
410
}
411
412
@injectable()
413
class EnterpriseTenantuService implements ITenantService {
414
getTenantData() { return { type: "enterprise", features: ["advanced"] }; }
415
}
416
417
@injectable()
418
class BasicTenantService implements ITenantService {
419
getTenantData() { return { type: "basic", features: ["standard"] }; }
420
}
421
422
// Complex tenant-based binding
423
container.bind<ITenantService>("TenantService")
424
.to(EnterpriseTenantService)
425
.when((request) => {
426
// Check if request comes from enterprise context
427
let current = request.currentRequest;
428
while (current) {
429
if (current.target?.hasTag("tenant", "enterprise")) {
430
return true;
431
}
432
current = current.parentRequest;
433
}
434
return false;
435
});
436
437
container.bind<ITenantService>("TenantService")
438
.to(BasicTenantService)
439
.when(() => true); // Default fallback
440
441
@injectable()
442
class EnterpriseController {
443
constructor(
444
@inject("TenantService") @tagged("tenant", "enterprise")
445
private tenantService: ITenantService
446
) {}
447
}
448
```
449
450
## Best Practices
451
452
1. **Use specific conditions**: Make conditional logic as specific as possible
453
2. **Order matters**: More specific bindings should be registered first
454
3. **Provide fallbacks**: Always have a default binding when using conditions
455
4. **Keep constraints simple**: Complex logic can impact performance
456
5. **Test conditional bindings**: Verify all conditional paths work correctly
457
6. **Document complex conditions**: Make conditional logic clear for maintainers
458
7. **Use named/tagged over custom**: Prefer declarative over imperative conditions
459
8. **Consider performance**: Complex constraint functions are evaluated at resolution time