0
# Lifecycle Management
1
2
InversifyJS provides comprehensive lifecycle management features including scope control, activation/deactivation hooks, construction/destruction callbacks, and container event handling. These features enable proper resource management and fine-grained control over service lifetimes.
3
4
## Lifecycle Scopes
5
6
### Binding Scope Values
7
8
```typescript { .api }
9
const bindingScopeValues: {
10
Singleton: "Singleton";
11
Transient: "Transient";
12
Request: "Request";
13
};
14
15
type BindingScope = keyof typeof bindingScopeValues;
16
```
17
18
### Singleton Scope
19
20
Single instance shared across the entire container lifetime.
21
22
```typescript
23
@injectable()
24
class DatabaseConnectionPool {
25
private connections: Connection[] = [];
26
private maxConnections = 10;
27
28
constructor() {
29
console.log("Creating connection pool...");
30
}
31
32
getConnection(): Connection {
33
if (this.connections.length < this.maxConnections) {
34
const connection = new Connection();
35
this.connections.push(connection);
36
return connection;
37
}
38
return this.connections[Math.floor(Math.random() * this.connections.length)];
39
}
40
41
@preDestroy()
42
cleanup() {
43
console.log("Closing all connections...");
44
this.connections.forEach(conn => conn.close());
45
}
46
}
47
48
// Single instance for entire application
49
container.bind<DatabaseConnectionPool>("ConnectionPool")
50
.to(DatabaseConnectionPool)
51
.inSingletonScope();
52
53
// All requests return same instance
54
const pool1 = container.get<DatabaseConnectionPool>("ConnectionPool");
55
const pool2 = container.get<DatabaseConnectionPool>("ConnectionPool");
56
console.log(pool1 === pool2); // true
57
```
58
59
### Transient Scope
60
61
New instance created for every resolution request.
62
63
```typescript
64
@injectable()
65
class TaskProcessor {
66
private taskId = Math.random().toString(36);
67
68
constructor() {
69
console.log(`Creating task processor: ${this.taskId}`);
70
}
71
72
process(data: any) {
73
console.log(`Processing with ${this.taskId}`);
74
return { processedBy: this.taskId, data };
75
}
76
77
@preDestroy()
78
cleanup() {
79
console.log(`Cleaning up processor: ${this.taskId}`);
80
}
81
}
82
83
// New instance every time
84
container.bind<TaskProcessor>("TaskProcessor")
85
.to(TaskProcessor)
86
.inTransientScope();
87
88
const processor1 = container.get<TaskProcessor>("TaskProcessor");
89
const processor2 = container.get<TaskProcessor>("TaskProcessor");
90
console.log(processor1 === processor2); // false
91
```
92
93
### Request Scope
94
95
Single instance per dependency resolution graph.
96
97
```typescript
98
@injectable()
99
class RequestContext {
100
private requestId = Math.random().toString(36);
101
private startTime = Date.now();
102
103
constructor() {
104
console.log(`Request context created: ${this.requestId}`);
105
}
106
107
getRequestId() { return this.requestId; }
108
getElapsedTime() { return Date.now() - this.startTime; }
109
110
@preDestroy()
111
cleanup() {
112
console.log(`Request completed in ${this.getElapsedTime()}ms`);
113
}
114
}
115
116
@injectable()
117
class UserService {
118
constructor(@inject("RequestContext") private context: RequestContext) {}
119
120
getUser(id: string) {
121
console.log(`UserService using context: ${this.context.getRequestId()}`);
122
return { id, requestId: this.context.getRequestId() };
123
}
124
}
125
126
@injectable()
127
class OrderService {
128
constructor(@inject("RequestContext") private context: RequestContext) {}
129
130
getOrder(id: string) {
131
console.log(`OrderService using context: ${this.context.getRequestId()}`);
132
return { id, requestId: this.context.getRequestId() };
133
}
134
}
135
136
container.bind<RequestContext>("RequestContext").to(RequestContext).inRequestScope();
137
container.bind<UserService>("UserService").to(UserService);
138
container.bind<OrderService>("OrderService").to(OrderService);
139
140
// Same context shared within single resolution request
141
const userService = container.get<UserService>("UserService");
142
const orderService = container.get<OrderService>("OrderService");
143
// Both services share the same RequestContext instance
144
145
// New context for new resolution request
146
const userService2 = container.get<UserService>("UserService");
147
// userService2 gets a different RequestContext instance
148
```
149
150
## Lifecycle Hook Decorators
151
152
### @postConstruct
153
154
Executed after object construction and all dependency injection is complete.
155
156
```typescript { .api }
157
function postConstruct(target: any, propertyKey: string): void;
158
```
159
160
```typescript
161
@injectable()
162
class EmailService {
163
@inject("Config") private config!: IConfig;
164
@inject("Logger") private logger!: ILogger;
165
166
private smtpClient?: SmtpClient;
167
private isInitialized = false;
168
169
@postConstruct()
170
private async initialize() {
171
this.logger.log("Initializing email service...");
172
173
this.smtpClient = new SmtpClient({
174
host: this.config.smtp.host,
175
port: this.config.smtp.port,
176
secure: this.config.smtp.secure
177
});
178
179
await this.smtpClient.connect();
180
this.isInitialized = true;
181
182
this.logger.log("Email service initialized successfully");
183
}
184
185
async sendEmail(to: string, subject: string, body: string) {
186
if (!this.isInitialized) {
187
throw new Error("Email service not initialized");
188
}
189
190
return this.smtpClient!.sendMail({ to, subject, html: body });
191
}
192
}
193
```
194
195
### @preDestroy
196
197
Executed before object destruction or container disposal.
198
199
```typescript { .api }
200
function preDestroy(target: any, propertyKey: string): void;
201
```
202
203
```typescript
204
@injectable()
205
class FileProcessingService {
206
private fileHandles: FileHandle[] = [];
207
private tempFiles: string[] = [];
208
private processingSessions = new Map<string, ProcessingSession>();
209
210
async processFile(filePath: string): Promise<ProcessingResult> {
211
const handle = await fs.open(filePath, 'r');
212
this.fileHandles.push(handle);
213
214
const tempFile = `/tmp/processing_${Date.now()}.tmp`;
215
this.tempFiles.push(tempFile);
216
217
const sessionId = Math.random().toString(36);
218
const session = new ProcessingSession(sessionId);
219
this.processingSessions.set(sessionId, session);
220
221
// Processing logic...
222
return { sessionId, processed: true };
223
}
224
225
@preDestroy()
226
private async cleanup() {
227
console.log("Cleaning up file processing service...");
228
229
// Close all file handles
230
for (const handle of this.fileHandles) {
231
try {
232
await handle.close();
233
} catch (error) {
234
console.error("Error closing file handle:", error);
235
}
236
}
237
238
// Remove temporary files
239
for (const tempFile of this.tempFiles) {
240
try {
241
await fs.unlink(tempFile);
242
} catch (error) {
243
console.error("Error removing temp file:", error);
244
}
245
}
246
247
// Clean up processing sessions
248
for (const [sessionId, session] of this.processingSessions) {
249
try {
250
await session.terminate();
251
} catch (error) {
252
console.error(`Error terminating session ${sessionId}:`, error);
253
}
254
}
255
256
console.log("File processing service cleanup completed");
257
}
258
}
259
```
260
261
## Activation and Deactivation Hooks
262
263
### OnActivation Interface
264
265
```typescript { .api }
266
interface OnActivation<T> {
267
(context: ResolutionContext, injectable: T): T | Promise<T>;
268
}
269
270
interface BindOnFluentSyntax<T> {
271
onActivation(handler: OnActivation<T>): BindInFluentSyntax<T>;
272
}
273
```
274
275
### Activation Handlers
276
277
Called immediately after service instantiation but before returning to requester.
278
279
```typescript
280
@injectable()
281
class CacheService {
282
private cache = new Map<string, any>();
283
284
get(key: string) { return this.cache.get(key); }
285
set(key: string, value: any) { this.cache.set(key, value); }
286
clear() { this.cache.clear(); }
287
}
288
289
container.bind<CacheService>("CacheService")
290
.to(CacheService)
291
.inSingletonScope()
292
.onActivation((context, cacheService) => {
293
console.log("Cache service activated");
294
295
// Pre-populate cache with initial data
296
cacheService.set("initialized", true);
297
cacheService.set("activatedAt", new Date().toISOString());
298
299
// Set up periodic cleanup
300
setInterval(() => {
301
console.log("Performing cache cleanup...");
302
// Cleanup logic here
303
}, 60000);
304
305
return cacheService;
306
});
307
308
// Async activation handler
309
container.bind<DatabaseService>("DatabaseService")
310
.to(DatabaseService)
311
.inSingletonScope()
312
.onActivation(async (context, dbService) => {
313
console.log("Activating database service...");
314
315
await dbService.connect();
316
await dbService.runMigrations();
317
318
console.log("Database service ready");
319
return dbService;
320
});
321
```
322
323
### Deactivation Handlers
324
325
```typescript { .api }
326
interface OnDeactivation<T> {
327
(injectable: T): void | Promise<void>;
328
}
329
330
interface BindOnFluentSyntax<T> {
331
onDeactivation(handler: OnDeactivation<T>): BindInFluentSyntax<T>;
332
}
333
```
334
335
Called before service destruction or container disposal.
336
337
```typescript
338
@injectable()
339
class WebSocketService {
340
private connections = new Set<WebSocket>();
341
private heartbeatInterval?: NodeJS.Timeout;
342
343
constructor() {
344
this.heartbeatInterval = setInterval(() => {
345
this.sendHeartbeat();
346
}, 30000);
347
}
348
349
addConnection(ws: WebSocket) {
350
this.connections.add(ws);
351
}
352
353
removeConnection(ws: WebSocket) {
354
this.connections.delete(ws);
355
}
356
357
private sendHeartbeat() {
358
for (const ws of this.connections) {
359
if (ws.readyState === WebSocket.OPEN) {
360
ws.ping();
361
}
362
}
363
}
364
}
365
366
container.bind<WebSocketService>("WebSocketService")
367
.to(WebSocketService)
368
.inSingletonScope()
369
.onDeactivation((wsService) => {
370
console.log("Deactivating WebSocket service...");
371
372
// Clear heartbeat interval
373
if (wsService.heartbeatInterval) {
374
clearInterval(wsService.heartbeatInterval);
375
}
376
377
// Close all connections
378
for (const ws of wsService.connections) {
379
if (ws.readyState === WebSocket.OPEN) {
380
ws.close(1000, "Service shutting down");
381
}
382
}
383
384
console.log("WebSocket service deactivated");
385
});
386
387
// Async deactivation handler
388
container.bind<DatabaseService>("DatabaseService")
389
.to(DatabaseService)
390
.inSingletonScope()
391
.onDeactivation(async (dbService) => {
392
console.log("Deactivating database service...");
393
394
await dbService.flushPendingOperations();
395
await dbService.disconnect();
396
397
console.log("Database service deactivated");
398
});
399
```
400
401
## Container Lifecycle Events
402
403
### Container Disposal
404
405
```typescript
406
// Container with lifecycle management
407
const container = new Container();
408
409
// Register services with lifecycle hooks
410
container.bind<FileService>("FileService")
411
.to(FileService)
412
.inSingletonScope()
413
.onDeactivation(async (service) => {
414
await service.closeAllFiles();
415
});
416
417
container.bind<NetworkService>("NetworkService")
418
.to(NetworkService)
419
.inSingletonScope()
420
.onDeactivation(async (service) => {
421
await service.closeConnections();
422
});
423
424
// Graceful shutdown
425
process.on('SIGTERM', async () => {
426
console.log('Received SIGTERM, shutting down gracefully...');
427
428
// This will trigger all deactivation handlers
429
await container.unbindAllAsync();
430
431
process.exit(0);
432
});
433
```
434
435
## Advanced Lifecycle Patterns
436
437
### Circular Dependency Handling
438
439
```typescript
440
@injectable()
441
class ServiceA {
442
private serviceB?: ServiceB;
443
444
@postConstruct()
445
initialize() {
446
// Safe to access circular dependencies in postConstruct
447
this.serviceB = container.get<ServiceB>("ServiceB");
448
}
449
}
450
451
@injectable()
452
class ServiceB {
453
private serviceA?: ServiceA;
454
455
@postConstruct()
456
initialize() {
457
this.serviceA = container.get<ServiceA>("ServiceA");
458
}
459
}
460
```
461
462
### Lazy Initialization
463
464
```typescript
465
@injectable()
466
class ExpensiveService {
467
private _heavyResource?: HeavyResource;
468
469
get heavyResource() {
470
if (!this._heavyResource) {
471
console.log("Lazy loading heavy resource...");
472
this._heavyResource = new HeavyResource();
473
}
474
return this._heavyResource;
475
}
476
477
@preDestroy()
478
cleanup() {
479
if (this._heavyResource) {
480
this._heavyResource.dispose();
481
}
482
}
483
}
484
```
485
486
### Health Check Integration
487
488
```typescript
489
@injectable()
490
class HealthCheckService {
491
private services = new Map<string, HealthCheckable>();
492
493
registerService(name: string, service: HealthCheckable) {
494
this.services.set(name, service);
495
}
496
497
async checkHealth(): Promise<HealthStatus> {
498
const results = new Map<string, boolean>();
499
500
for (const [name, service] of this.services) {
501
try {
502
const isHealthy = await service.isHealthy();
503
results.set(name, isHealthy);
504
} catch (error) {
505
results.set(name, false);
506
}
507
}
508
509
return {
510
overall: Array.from(results.values()).every(Boolean),
511
services: Object.fromEntries(results)
512
};
513
}
514
}
515
516
// Register services with health checks via activation handlers
517
container.bind<DatabaseService>("DatabaseService")
518
.to(DatabaseService)
519
.inSingletonScope()
520
.onActivation((context, dbService) => {
521
const healthCheck = context.container.get<HealthCheckService>("HealthCheck");
522
healthCheck.registerService("database", dbService);
523
return dbService;
524
});
525
```
526
527
## Best Practices
528
529
1. **Use appropriate scopes**: Match scope to service purpose and resource usage
530
2. **Implement @preDestroy**: Always clean up resources in preDestroy methods
531
3. **Avoid heavy work in constructors**: Use @postConstruct for complex initialization
532
4. **Handle async operations**: Use async/await in lifecycle hooks when needed
533
5. **Register health checks**: Use activation handlers to integrate with health monitoring
534
6. **Graceful shutdown**: Implement proper container disposal on application exit
535
7. **Test lifecycle**: Verify construction, initialization, and cleanup work correctly
536
8. **Document dependencies**: Make initialization order dependencies clear