or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

binding.mdconditional.mdcontainer.mddecorators.mdindex.mdlifecycle.mdmodules.md
tile.json

conditional.mddocs/

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