Skip to content

Lifecycle Hooks

Services can implement lifecycle hooks for initialization and cleanup.

onInit Hook

Called after a service instance is created:

typescript
class DatabaseConnection {
  private connection: any;

  async onInit() {
    console.log('Initializing database connection...');
    this.connection = await connectToDatabase();
    console.log('Database connected!');
  }

  query(sql: string) {
    return this.connection.query(sql);
  }
}

services.addScoped<DatabaseConnection>(DatabaseConnectionToken, DatabaseConnection);

const scope = provider.createScope();
const db = await scope.getRequiredService<DatabaseConnection>(DatabaseConnectionToken);
// onInit() is automatically called

onDestroy Hook

Called when a scope is disposed:

typescript
class DatabaseConnection {
  private connection: any;

  async onInit() {
    this.connection = await connectToDatabase();
  }

  async onDestroy() {
    if (this.connection) {
      await this.connection.close();
      console.log('Database connection closed');
    }
  }
}

const scope = provider.createScope();
const db = await scope.getRequiredService<DatabaseConnection>(DatabaseConnectionToken);

// ... use database

await scope.dispose(); // onDestroy() is automatically called

Both Hooks

Services can implement both hooks:

typescript
class CacheService {
  private cache = new Map<string, any>();

  async onInit() {
    console.log('Cache initialized');
    // Load cache from disk, etc.
  }

  async onDestroy() {
    console.log('Clearing cache...');
    this.cache.clear();
    // Save cache to disk, etc.
  }
}

Hook Execution Order

  1. Service instance is created
  2. Dependencies are injected
  3. onInit() is called (if implemented)
  4. Service is ready to use
  5. When scope is disposed, onDestroy() is called (if implemented)

Async Hooks

Both hooks can be async:

typescript
class DatabaseConnection {
  async onInit() {
    await this.connect();
    await this.migrate();
  }

  async onDestroy() {
    await this.close();
    await this.cleanup();
  }
}

Error Handling

If onInit() throws, the service creation fails:

typescript
class DatabaseConnection {
  async onInit() {
    throw new Error('Failed to connect');
  }
}

// This will throw the error
const db = await provider.getRequiredService<DatabaseConnection>(DatabaseConnectionToken);

If onDestroy() throws, the error is logged but doesn't prevent disposal:

typescript
class Service {
  async onDestroy() {
    throw new Error('Cleanup failed');
  }
}

// Error is logged, but disposal continues
await scope.dispose();

Use Cases

onInit

  • Database connections
  • Cache initialization
  • Loading configuration
  • Setting up event listeners

onDestroy

  • Closing database connections
  • Clearing caches
  • Removing event listeners
  • Saving state

Best Practices

  1. Keep hooks simple - Avoid complex logic
  2. Handle errors - Wrap in try-catch if needed
  3. Make hooks async - For async operations
  4. Clean up in onDestroy - Prevent resource leaks
  5. Test hooks - Ensure they work correctly

Next Steps

Released under the ISC License. If you find this project helpful, consider buying me a coffee