Appearance
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