0
# Lazy Loading
1
2
Lazy initialization utilities for circular dependency resolution, delayed constructor instantiation, and performance optimization through deferred object creation.
3
4
## Capabilities
5
6
### Delayed Constructor
7
8
Proxy-based class for lazy loading that defers constructor resolution until the instance is actually used.
9
10
```typescript { .api }
11
/**
12
* Proxy-based delayed constructor for lazy loading
13
* Defers constructor resolution until instance creation
14
*/
15
class DelayedConstructor<T> {
16
/**
17
* Creates proxy instance that delays object creation
18
* @param createObject - Function to create the actual instance
19
* @returns Proxy instance of type T
20
*/
21
createProxy(createObject: (ctor: constructor<T>) => T): T;
22
}
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
// Manual DelayedConstructor usage (typically internal)
29
const delayedUserService = new DelayedConstructor<UserService>();
30
31
const userServiceProxy = delayedUserService.createProxy((ctor) => {
32
// This function is called only when the instance is first accessed
33
console.log("Creating UserService instance");
34
return new ctor(logger, database);
35
});
36
37
// userServiceProxy acts like UserService but creation is deferred
38
// until first method call or property access
39
```
40
41
### Delay Function
42
43
Primary utility function for creating delayed constructors that resolve circular dependencies and enable lazy instantiation.
44
45
```typescript { .api }
46
/**
47
* Creates delayed constructor for lazy loading and circular dependency resolution
48
* Constructor function is provided via callback, enabling forward references
49
* @param wrappedConstructor - Function returning the actual constructor
50
* @returns DelayedConstructor instance for lazy instantiation
51
*/
52
function delay<T>(wrappedConstructor: () => constructor<T>): DelayedConstructor<T>;
53
```
54
55
**Usage Examples:**
56
57
```typescript
58
// Circular dependency resolution
59
class UserService {
60
constructor(
61
private orderService: OrderService,
62
private logger: Logger
63
) {}
64
65
getUserOrders(userId: string) {
66
return this.orderService.getOrdersByUser(userId);
67
}
68
}
69
70
class OrderService {
71
constructor(
72
private userService: UserService, // Circular dependency!
73
private database: Database
74
) {}
75
76
getOrdersByUser(userId: string) {
77
const user = this.userService.getUser(userId);
78
return this.database.getOrders({ userId: user.id });
79
}
80
81
getUser(userId: string) {
82
// This creates a circular call, but delay() resolves it
83
return this.userService.getUser(userId);
84
}
85
}
86
87
// Register with delayed constructor to break circular dependency
88
container.register("UserService", UserService);
89
container.register("OrderService", {
90
useClass: delay(() => OrderService) // Delay resolves circular reference
91
});
92
93
// Forward reference resolution
94
// Useful when classes are defined in dependency order
95
container.register("EarlyService", {
96
useClass: delay(() => LateService) // LateService defined later in file
97
});
98
99
@injectable()
100
class EarlyService {
101
constructor(private lateService: LateService) {}
102
}
103
104
// LateService defined after EarlyService
105
@injectable()
106
class LateService {
107
doSomething() {
108
return "Late service action";
109
}
110
}
111
```
112
113
### Lazy Loading Patterns
114
115
Common patterns and best practices for implementing lazy loading with TSyringe.
116
117
**Usage Examples:**
118
119
```typescript
120
// Lazy singleton with expensive initialization
121
const expensiveSingletonDelay = delay(() => {
122
// This code only runs when the service is first resolved
123
console.log("Initializing expensive singleton");
124
125
return class ExpensiveService {
126
private data: LargeDataSet;
127
128
constructor() {
129
// Expensive initialization
130
this.data = this.loadLargeDataSet();
131
}
132
133
private loadLargeDataSet(): LargeDataSet {
134
// Simulate expensive operation
135
return new LargeDataSet();
136
}
137
138
processData(input: any) {
139
return this.data.process(input);
140
}
141
};
142
});
143
144
container.register("ExpensiveService", {
145
useClass: expensiveSingletonDelay
146
}, { lifecycle: Lifecycle.Singleton });
147
148
// Conditional lazy loading
149
const conditionalServiceDelay = delay(() => {
150
// Determine service type at resolution time
151
const env = process.env.NODE_ENV;
152
153
if (env === "production") {
154
return ProductionService;
155
} else if (env === "test") {
156
return MockService;
157
} else {
158
return DevelopmentService;
159
}
160
});
161
162
container.register("ConditionalService", {
163
useClass: conditionalServiceDelay
164
});
165
166
// Plugin system with lazy loading
167
interface Plugin {
168
name: string;
169
execute(): void;
170
}
171
172
// Plugins are loaded only when needed
173
const pluginDelays = [
174
delay(() => class EmailPlugin implements Plugin {
175
name = "email";
176
execute() { console.log("Sending email"); }
177
}),
178
delay(() => class SmsPlugin implements Plugin {
179
name = "sms";
180
execute() { console.log("Sending SMS"); }
181
}),
182
delay(() => class PushPlugin implements Plugin {
183
name = "push";
184
execute() { console.log("Sending push notification"); }
185
})
186
];
187
188
// Register plugins lazily
189
pluginDelays.forEach((pluginDelay, index) => {
190
container.register(`Plugin${index}`, { useClass: pluginDelay });
191
});
192
193
// Lazy dependency injection with optional dependencies
194
@injectable()
195
class ServiceWithOptionalDependencies {
196
constructor(
197
private required: RequiredService,
198
@inject("OptionalService") private optional?: OptionalService
199
) {}
200
201
performAction() {
202
this.required.doRequiredWork();
203
204
// Optional service is only resolved if registered
205
if (this.optional) {
206
this.optional.doOptionalWork();
207
}
208
}
209
}
210
211
// Register optional service with delay for performance
212
container.register("OptionalService", {
213
useClass: delay(() => {
214
// Only load this service if it's actually used
215
console.log("Loading optional service");
216
return OptionalService;
217
})
218
});
219
```
220
221
### Circular Dependency Resolution
222
223
Advanced patterns for resolving complex circular dependencies.
224
225
**Usage Examples:**
226
227
```typescript
228
// Complex circular dependency scenario
229
interface IUserService {
230
getUser(id: string): User;
231
getUserWithOrders(id: string): UserWithOrders;
232
}
233
234
interface IOrderService {
235
getOrder(id: string): Order;
236
getOrdersForUser(userId: string): Order[];
237
}
238
239
interface INotificationService {
240
notifyUser(userId: string, message: string): void;
241
notifyOrderUpdate(orderId: string): void;
242
}
243
244
@injectable()
245
class UserService implements IUserService {
246
constructor(
247
@inject("IOrderService") private orderService: IOrderService,
248
@inject("INotificationService") private notificationService: INotificationService
249
) {}
250
251
getUser(id: string): User {
252
return { id, name: `User ${id}` };
253
}
254
255
getUserWithOrders(id: string): UserWithOrders {
256
const user = this.getUser(id);
257
const orders = this.orderService.getOrdersForUser(id);
258
return { ...user, orders };
259
}
260
}
261
262
@injectable()
263
class OrderService implements IOrderService {
264
constructor(
265
@inject("IUserService") private userService: IUserService,
266
@inject("INotificationService") private notificationService: INotificationService
267
) {}
268
269
getOrder(id: string): Order {
270
return { id, amount: 100 };
271
}
272
273
getOrdersForUser(userId: string): Order[] {
274
// Validate user exists (circular call)
275
const user = this.userService.getUser(userId);
276
return [{ id: "1", amount: 100 }];
277
}
278
}
279
280
@injectable()
281
class NotificationService implements INotificationService {
282
constructor(
283
@inject("IUserService") private userService: IUserService,
284
@inject("IOrderService") private orderService: IOrderService
285
) {}
286
287
notifyUser(userId: string, message: string): void {
288
const user = this.userService.getUser(userId);
289
console.log(`Notifying ${user.name}: ${message}`);
290
}
291
292
notifyOrderUpdate(orderId: string): void {
293
const order = this.orderService.getOrder(orderId);
294
console.log(`Order ${order.id} updated`);
295
}
296
}
297
298
// Register services with delays to break circular dependencies
299
container.register("IUserService", UserService);
300
container.register("IOrderService", {
301
useClass: delay(() => OrderService)
302
});
303
container.register("INotificationService", {
304
useClass: delay(() => NotificationService)
305
});
306
307
// All services can now be resolved successfully
308
const userService = container.resolve<IUserService>("IUserService");
309
const userWithOrders = userService.getUserWithOrders("123");
310
```
311
312
## Types
313
314
```typescript { .api }
315
// Delayed constructor class
316
class DelayedConstructor<T> {
317
createProxy(createObject: (ctor: constructor<T>) => T): T;
318
}
319
320
// Delay function signature
321
function delay<T>(wrappedConstructor: () => constructor<T>): DelayedConstructor<T>;
322
323
// Constructor type
324
type constructor<T> = {new (...args: any[]): T};
325
326
// Wrapped constructor function type
327
type WrappedConstructor<T> = () => constructor<T>;
328
329
// Proxy creation function type
330
type ProxyCreationFunction<T> = (ctor: constructor<T>) => T;
331
```