CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-tsyringe

Lightweight dependency injection container for JavaScript/TypeScript

Pending
Overview
Eval results
Files

lazy-loading.mddocs/

Lazy Loading

Lazy initialization utilities for circular dependency resolution, delayed constructor instantiation, and performance optimization through deferred object creation.

Capabilities

Delayed Constructor

Proxy-based class for lazy loading that defers constructor resolution until the instance is actually used.

/**
 * Proxy-based delayed constructor for lazy loading
 * Defers constructor resolution until instance creation
 */
class DelayedConstructor<T> {
  /**
   * Creates proxy instance that delays object creation
   * @param createObject - Function to create the actual instance
   * @returns Proxy instance of type T
   */
  createProxy(createObject: (ctor: constructor<T>) => T): T;
}

Usage Examples:

// Manual DelayedConstructor usage (typically internal)
const delayedUserService = new DelayedConstructor<UserService>();

const userServiceProxy = delayedUserService.createProxy((ctor) => {
  // This function is called only when the instance is first accessed
  console.log("Creating UserService instance");
  return new ctor(logger, database);
});

// userServiceProxy acts like UserService but creation is deferred
// until first method call or property access

Delay Function

Primary utility function for creating delayed constructors that resolve circular dependencies and enable lazy instantiation.

/**
 * Creates delayed constructor for lazy loading and circular dependency resolution
 * Constructor function is provided via callback, enabling forward references
 * @param wrappedConstructor - Function returning the actual constructor
 * @returns DelayedConstructor instance for lazy instantiation
 */
function delay<T>(wrappedConstructor: () => constructor<T>): DelayedConstructor<T>;

Usage Examples:

// Circular dependency resolution
class UserService {
  constructor(
    private orderService: OrderService,
    private logger: Logger
  ) {}
  
  getUserOrders(userId: string) {
    return this.orderService.getOrdersByUser(userId);
  }
}

class OrderService {
  constructor(
    private userService: UserService, // Circular dependency!
    private database: Database
  ) {}
  
  getOrdersByUser(userId: string) {
    const user = this.userService.getUser(userId);
    return this.database.getOrders({ userId: user.id });
  }
  
  getUser(userId: string) {
    // This creates a circular call, but delay() resolves it
    return this.userService.getUser(userId);
  }
}

// Register with delayed constructor to break circular dependency
container.register("UserService", UserService);
container.register("OrderService", {
  useClass: delay(() => OrderService) // Delay resolves circular reference
});

// Forward reference resolution
// Useful when classes are defined in dependency order
container.register("EarlyService", {
  useClass: delay(() => LateService) // LateService defined later in file
});

@injectable()
class EarlyService {
  constructor(private lateService: LateService) {}
}

// LateService defined after EarlyService
@injectable()
class LateService {
  doSomething() {
    return "Late service action";
  }
}

Lazy Loading Patterns

Common patterns and best practices for implementing lazy loading with TSyringe.

Usage Examples:

// Lazy singleton with expensive initialization
const expensiveSingletonDelay = delay(() => {
  // This code only runs when the service is first resolved
  console.log("Initializing expensive singleton");
  
  return class ExpensiveService {
    private data: LargeDataSet;
    
    constructor() {
      // Expensive initialization
      this.data = this.loadLargeDataSet();
    }
    
    private loadLargeDataSet(): LargeDataSet {
      // Simulate expensive operation
      return new LargeDataSet();
    }
    
    processData(input: any) {
      return this.data.process(input);
    }
  };
});

container.register("ExpensiveService", {
  useClass: expensiveSingletonDelay
}, { lifecycle: Lifecycle.Singleton });

// Conditional lazy loading
const conditionalServiceDelay = delay(() => {
  // Determine service type at resolution time
  const env = process.env.NODE_ENV;
  
  if (env === "production") {
    return ProductionService;
  } else if (env === "test") {
    return MockService;
  } else {
    return DevelopmentService;
  }
});

container.register("ConditionalService", {
  useClass: conditionalServiceDelay
});

// Plugin system with lazy loading
interface Plugin {
  name: string;
  execute(): void;
}

// Plugins are loaded only when needed
const pluginDelays = [
  delay(() => class EmailPlugin implements Plugin {
    name = "email";
    execute() { console.log("Sending email"); }
  }),
  delay(() => class SmsPlugin implements Plugin {
    name = "sms";
    execute() { console.log("Sending SMS"); }
  }),
  delay(() => class PushPlugin implements Plugin {
    name = "push";
    execute() { console.log("Sending push notification"); }
  })
];

// Register plugins lazily
pluginDelays.forEach((pluginDelay, index) => {
  container.register(`Plugin${index}`, { useClass: pluginDelay });
});

// Lazy dependency injection with optional dependencies
@injectable()
class ServiceWithOptionalDependencies {
  constructor(
    private required: RequiredService,
    @inject("OptionalService") private optional?: OptionalService
  ) {}
  
  performAction() {
    this.required.doRequiredWork();
    
    // Optional service is only resolved if registered
    if (this.optional) {
      this.optional.doOptionalWork();
    }
  }
}

// Register optional service with delay for performance
container.register("OptionalService", {
  useClass: delay(() => {
    // Only load this service if it's actually used
    console.log("Loading optional service");
    return OptionalService;
  })
});

Circular Dependency Resolution

Advanced patterns for resolving complex circular dependencies.

Usage Examples:

// Complex circular dependency scenario
interface IUserService {
  getUser(id: string): User;
  getUserWithOrders(id: string): UserWithOrders;
}

interface IOrderService {
  getOrder(id: string): Order;
  getOrdersForUser(userId: string): Order[];
}

interface INotificationService {
  notifyUser(userId: string, message: string): void;
  notifyOrderUpdate(orderId: string): void;
}

@injectable()
class UserService implements IUserService {
  constructor(
    @inject("IOrderService") private orderService: IOrderService,
    @inject("INotificationService") private notificationService: INotificationService
  ) {}
  
  getUser(id: string): User {
    return { id, name: `User ${id}` };
  }
  
  getUserWithOrders(id: string): UserWithOrders {
    const user = this.getUser(id);
    const orders = this.orderService.getOrdersForUser(id);
    return { ...user, orders };
  }
}

@injectable()
class OrderService implements IOrderService {
  constructor(
    @inject("IUserService") private userService: IUserService,
    @inject("INotificationService") private notificationService: INotificationService
  ) {}
  
  getOrder(id: string): Order {
    return { id, amount: 100 };
  }
  
  getOrdersForUser(userId: string): Order[] {
    // Validate user exists (circular call)
    const user = this.userService.getUser(userId);
    return [{ id: "1", amount: 100 }];
  }
}

@injectable()
class NotificationService implements INotificationService {
  constructor(
    @inject("IUserService") private userService: IUserService,
    @inject("IOrderService") private orderService: IOrderService
  ) {}
  
  notifyUser(userId: string, message: string): void {
    const user = this.userService.getUser(userId);
    console.log(`Notifying ${user.name}: ${message}`);
  }
  
  notifyOrderUpdate(orderId: string): void {
    const order = this.orderService.getOrder(orderId);
    console.log(`Order ${order.id} updated`);
  }
}

// Register services with delays to break circular dependencies
container.register("IUserService", UserService);
container.register("IOrderService", {
  useClass: delay(() => OrderService)
});
container.register("INotificationService", {
  useClass: delay(() => NotificationService)
});

// All services can now be resolved successfully
const userService = container.resolve<IUserService>("IUserService");
const userWithOrders = userService.getUserWithOrders("123");

Types

// Delayed constructor class
class DelayedConstructor<T> {
  createProxy(createObject: (ctor: constructor<T>) => T): T;
}

// Delay function signature
function delay<T>(wrappedConstructor: () => constructor<T>): DelayedConstructor<T>;

// Constructor type
type constructor<T> = {new (...args: any[]): T};

// Wrapped constructor function type
type WrappedConstructor<T> = () => constructor<T>;

// Proxy creation function type
type ProxyCreationFunction<T> = (ctor: constructor<T>) => T;

Install with Tessl CLI

npx tessl i tessl/npm-tsyringe

docs

decorators.md

dependency-container.md

factories.md

index.md

lazy-loading.md

lifecycle-management.md

providers.md

tile.json