Skip to content

Service Lifecycle Hooks

Lifecycle hooks for managing service initialization and termination processes.

🎯 What is it?

Lifecycle Hooks are methods that run automatically at specific lifecycle moments of services. They allow you to perform custom operations when a service is initialized or terminated.

Hook Types

  • OnServiceInit: Runs when the service is first created
  • OnServiceDestroy: Runs before the service is destroyed
  • ServiceLifecycle: Contains both hooks

📦 Import

typescript
import { 
  OnServiceInit, 
  OnServiceDestroy, 
  ServiceLifecycle,
  xinjectable 
} from '@xcons/widget';

🔧 OnServiceInit - Initialization

Runs when the service is initialized. Ideal for initialization operations like establishing connections, loading configuration.

Basic Usage

typescript
@xinjectable()
export class DatabaseService implements OnServiceInit {
  private connection: any;

  onServiceInit(): void {
    console.log('Database initializing...');
    this.connection = this.createConnection();
  }

  private createConnection() {
    return { connected: true, host: 'localhost' };
  }
}

Async Initialization

typescript
@xinjectable()
export class ConfigService implements OnServiceInit {
  private config: any = null;

  async onServiceInit(): Promise<void> {
    try {
      this.config = await fetch('/api/config').then(r => r.json());
      console.log('Config loaded');
    } catch (error) {
      this.config = { apiUrl: 'http://localhost:3000' }; // Fallback
    }
  }
}

🗑️ OnServiceDestroy - Cleanup

Runs before the service is destroyed. Used for closing connections, cleaning up resources.

Basic Cleanup

typescript
@xinjectable()
export class TimerService implements OnServiceDestroy {
  private intervals: NodeJS.Timeout[] = [];

  startTimer(interval: number, callback: Function) {
    const id = setInterval(callback, interval);
    this.intervals.push(id);
    return id;
  }

  onServiceDestroy(): void {
    console.log('Timer service shutting down...');
    
    // Clear all timers
    this.intervals.forEach(id => clearInterval(id));
    this.intervals = [];
  }
}

Async Cleanup

typescript
@xinjectable()
export class DatabaseService implements OnServiceDestroy {
  private connections: any[] = [];

  async onServiceDestroy(): Promise<void> {
    console.log('Closing connections...');

    // Close all connections
    await Promise.all(
      this.connections.map(conn => conn.close())
    );

    this.connections = [];
  }
}

🔄 ServiceLifecycle - Combined Usage

Manage both initialization and cleanup operations together.

typescript
@xinjectable()
export class CacheService implements ServiceLifecycle {
  private cache = new Map<string, any>();
  private cleanupInterval?: NodeJS.Timeout;

  async onServiceInit(): Promise<void> {
    // Load cache
    const saved = localStorage.getItem('cache');
    if (saved) {
      const data = JSON.parse(saved);
      Object.entries(data).forEach(([k, v]) => this.cache.set(k, v));
    }

    // Start periodic cleanup
    this.cleanupInterval = setInterval(() => {
      this.cleanExpired();
    }, 60000); // Every minute
  }

  async onServiceDestroy(): Promise<void> {
    // Stop timer
    if (this.cleanupInterval) {
      clearInterval(this.cleanupInterval);
    }

    // Save cache
    const data = Object.fromEntries(this.cache.entries());
    localStorage.setItem('cache', JSON.stringify(data));

    // Clear
    this.cache.clear();
  }

  private cleanExpired(): void {
    const now = Date.now();
    this.cache.forEach((value, key) => {
      if (value.expiry && now > value.expiry) {
        this.cache.delete(key);
      }
    });
  }

  set(key: string, value: any, ttl?: number): void {
    this.cache.set(key, {
      data: value,
      expiry: ttl ? Date.now() + ttl : null
    });
  }

  get(key: string): any {
    const entry = this.cache.get(key);
    if (!entry) return null;

    if (entry.expiry && Date.now() > entry.expiry) {
      this.cache.delete(key);
      return null;
    }

    return entry.data;
  }
}

🔧 Scope Based Cleanup

Lifecycle hooks run automatically when cleaning services in a specific scope.

typescript
import { ServiceRegistry } from '@xcons/widget';

const registry = ServiceRegistry.getInstance();

// Clear scoped services (onServiceDestroy is called automatically)
registry.clearScope('scoped');

// Clear all services
registry.clear();

🛠️ Error Handling

Error management in lifecycle hooks:

typescript
@xinjectable()
export class RobustService implements ServiceLifecycle {
  async onServiceInit(): Promise<void> {
    try {
      await this.criticalInit();
    } catch (error) {
      console.error('Initialization error:', error);
      this.fallbackInit(); // Fallback
    }
  }

  async onServiceDestroy(): Promise<void> {
    const tasks = [
      this.closeDatabase(),
      this.clearCache(),
      this.closeConnections()
    ];

    // Handle each task separately
    const results = await Promise.allSettled(tasks);
    
    results.forEach((result, index) => {
      if (result.status === 'rejected') {
        console.error(`Cleanup ${index} failed:`, result.reason);
      }
    });
  }

  private async criticalInit(): Promise<void> {
    // Critical init logic
  }

  private fallbackInit(): void {
    // Safe fallback
  }

  private async closeDatabase(): Promise<void> {}
  private async clearCache(): Promise<void> {}
  private async closeConnections(): Promise<void> {}
}

📋 Best Practices

1. Always Clean Up

typescript
@xinjectable()
export class EventService implements ServiceLifecycle {
  private listeners: Function[] = [];

  onServiceInit(): void {
    window.addEventListener('resize', this.handleResize);
  }

  onServiceDestroy(): void {
    // Clean up event listeners
    window.removeEventListener('resize', this.handleResize);
    this.listeners = [];
  }

  private handleResize = () => {
    // Handle resize
  }
}

2. Use Async Hooks Correctly

typescript
@xinjectable()
export class DataService implements OnServiceInit {
  private data: any[] = [];

  async onServiceInit(): Promise<void> {
    // Await async operations
    this.data = await this.fetchData();
  }

  private async fetchData(): Promise<any[]> {
    const response = await fetch('/api/data');
    return response.json();
  }
}

3. Graceful Shutdown

typescript
@xinjectable()
export class AppService implements ServiceLifecycle {
  private isShuttingDown = false;

  async onServiceInit(): Promise<void> {
    // Listen for shutdown signals
    process.on('SIGTERM', () => this.shutdown());
  }

  async onServiceDestroy(): Promise<void> {
    await this.shutdown();
  }

  private async shutdown(): Promise<void> {
    if (this.isShuttingDown) return;
    
    this.isShuttingDown = true;
    console.log('Graceful shutdown...');
    
    // Wait for active operations
    await this.waitForActiveOperations();
  }

  private async waitForActiveOperations(): Promise<void> {
    // Active operations logic
  }
}

🧪 Usage with Testing

typescript
class MockService implements ServiceLifecycle {
  public initCalled = false;
  public destroyCalled = false;
  
  onServiceInit(): void {
    this.initCalled = true;
  }

  onServiceDestroy(): void {
    this.destroyCalled = true;
  }
}

describe('Lifecycle Hooks', () => {
  let service: MockService;
  let registry: ServiceRegistry;

  beforeEach(() => {
    registry = ServiceRegistry.getInstance();
    registry.clear();
    service = new MockService();
  });

  it('should call lifecycle hooks', () => {
    registry.registerService(MockService);
    const instance = registry.getService(MockService);
    
    expect(instance.initCalled).toBe(true);
    
    registry.clear();
    expect(instance.destroyCalled).toBe(true);
  });
});

✨ Summary

With Lifecycle Hooks:

  • ✅ Initialize services properly
  • ✅ Release resources cleanly
  • ✅ Prevent memory leaks
  • ✅ Provide graceful shutdown
  • ✅ Write testable code