EventEmitter API Reference
The EventEmitter class provides a publish-subscribe event system for lifecycle hooks and custom events.
Table of Contents
Overview
The EventEmitter enables:
- Event Subscription - Listen to events with
on() - Event Emission - Trigger events with
emit() - One-Time Listeners - Subscribe with
once() - Unsubscription - Remove listeners with
off() - Lifecycle Hooks - Built-in DataSource events
- Custom Events - Create your own events
import { EventEmitter } from 'bindra';Constructor
constructor()Creates a new EventEmitter instance.
Example:
const emitter = new EventEmitter();Note: You typically don't create EventEmitter instances directly. DataSource extends EventEmitter, so all DataSource instances have event capabilities.
Methods
on()
Subscribe to an event.
on(event: string, callback: (...args: any[]) => void): voidParameters
- event (
string) - Event name to listen for - callback (
Function) - Function called when event is emitted
Example
emitter.on('update', (data) => {
console.log('Update event:', data);
});
emitter.emit('update', { id: 1, name: 'Alice' });
// Logs: "Update event: { id: 1, name: 'Alice' }"off()
Unsubscribe from an event.
off(event: string, callback: (...args: any[]) => void): voidParameters
- event (
string) - Event name - callback (
Function) - The exact callback to remove
Example
const handleUpdate = (data) => {
console.log('Updated:', data);
};
// Subscribe
emitter.on('update', handleUpdate);
// Later... unsubscribe
emitter.off('update', handleUpdate);
emitter.emit('update', { id: 1 });
// Nothing logged - unsubscribedImportant: You must pass the exact same function reference to off().
once()
Subscribe to an event once (auto-unsubscribes after first call).
once(event: string, callback: (...args: any[]) => void): voidParameters
- event (
string) - Event name - callback (
Function) - Function called once
Example
emitter.once('ready', () => {
console.log('Ready!');
});
emitter.emit('ready'); // Logs: "Ready!"
emitter.emit('ready'); // Nothing logged (already fired)emit()
Emit an event to all subscribers.
emit(event: string, ...args: any[]): voidParameters
- event (
string) - Event name to emit - ...args - Arguments passed to callbacks
Example
// No arguments
emitter.emit('start');
// One argument
emitter.emit('update', { id: 1 });
// Multiple arguments
emitter.emit('change', 'name', 'Alice', 'Bob');Event Patterns
DataSource Lifecycle Events
DataSource emits these events during its lifecycle:
CRUD Events
beforeFetch - Before fetching data
ds.on('beforeFetch', (query) => {
console.log('Fetching with query:', query);
});afterFetch - After fetching data
ds.on('afterFetch', (data) => {
console.log('Fetched data:', data);
});beforeCreate - Before creating a record
ds.on('beforeCreate', (record) => {
console.log('Creating:', record);
});afterCreate - After creating a record
ds.on('afterCreate', (record) => {
console.log('Created:', record);
});beforeUpdate - Before updating a record
ds.on('beforeUpdate', ({ id, changes }) => {
console.log('Updating record', id, 'with', changes);
});afterUpdate - After updating a record
ds.on('afterUpdate', (record) => {
console.log('Updated:', record);
});beforeDelete - Before deleting a record
ds.on('beforeDelete', (id) => {
console.log('Deleting:', id);
});afterDelete - After deleting a record
ds.on('afterDelete', (id) => {
console.log('Deleted:', id);
});Navigation Events
navigate - When current record changes
ds.on('navigate', (index, record) => {
console.log('Navigated to index', index, ':', record);
});Error Events
error - When an error occurs
ds.on('error', (error) => {
console.error('Error:', error);
});Real-time Events
realtime:connected - WebSocket connected
ds.on('realtime:connected', () => {
console.log('WebSocket connected');
});realtime:disconnected - WebSocket disconnected
ds.on('realtime:disconnected', () => {
console.log('WebSocket disconnected');
});realtime:message - WebSocket message received
ds.on('realtime:message', (message) => {
console.log('Received:', message);
});Usage Examples
Basic Event Handling
import { EventEmitter } from 'bindra';
const emitter = new EventEmitter();
// Subscribe to event
emitter.on('message', (text) => {
console.log('Message:', text);
});
// Emit event
emitter.emit('message', 'Hello, World!');
// Logs: "Message: Hello, World!"DataSource Events
import { DataSource } from 'bindra';
interface User {
id: number;
name: string;
}
const users = new DataSource<User>({
url: '/api/users'
});
// Listen to fetch events
users.on('beforeFetch', () => {
console.log('Loading users...');
showLoadingSpinner();
});
users.on('afterFetch', (data) => {
console.log('Loaded', data.length, 'users');
hideLoadingSpinner();
});
// Listen to create events
users.on('afterCreate', (user) => {
console.log('New user created:', user.name);
showNotification(`Welcome, ${user.name}!`);
});
// Fetch data
await users.fetch();Error Handling
const users = new DataSource<User>({ url: '/api/users' });
// Global error handler
users.on('error', (error) => {
console.error('DataSource error:', error);
if (error instanceof NetworkError) {
showErrorMessage('Network error. Please check your connection.');
} else if (error instanceof ValidationError) {
showErrorMessage('Validation failed: ' + error.message);
} else {
showErrorMessage('An unexpected error occurred.');
}
});
// Try to create invalid user
try {
await users.create({ name: '' }); // Validation error
} catch (error) {
// Error also emitted via 'error' event
}Multiple Listeners
const users = new DataSource<User>({ url: '/api/users' });
// Multiple listeners for same event
users.on('afterCreate', (user) => {
console.log('User created');
});
users.on('afterCreate', (user) => {
updateUserList(user);
});
users.on('afterCreate', (user) => {
logToAnalytics('user_created', { id: user.id });
});
users.on('afterCreate', (user) => {
sendWelcomeEmail(user.email);
});
// All four listeners fire when user is created
await users.create({ name: 'Alice', email: 'alice@example.com' });One-Time Events
const users = new DataSource<User>({ url: '/api/users' });
// Listen for first fetch only
users.once('afterFetch', (data) => {
console.log('Initial data loaded:', data.length, 'users');
initializeUI(data);
});
await users.fetch(); // Logs initial data
await users.fetch(); // Nothing logged (already fired)Cleanup / Unsubscribe
const users = new DataSource<User>({ url: '/api/users' });
// Create handler
const handleUpdate = (user: User) => {
console.log('User updated:', user);
updateUI(user);
};
// Subscribe
users.on('afterUpdate', handleUpdate);
// Later... cleanup
users.off('afterUpdate', handleUpdate);
// Or store reference for cleanup
function setupListeners() {
const handlers = {
fetch: (data: User[]) => console.log('Fetched:', data.length),
create: (user: User) => console.log('Created:', user.name),
update: (user: User) => console.log('Updated:', user.name)
};
users.on('afterFetch', handlers.fetch);
users.on('afterCreate', handlers.create);
users.on('afterUpdate', handlers.update);
return () => {
users.off('afterFetch', handlers.fetch);
users.off('afterCreate', handlers.create);
users.off('afterUpdate', handlers.update);
};
}
const cleanup = setupListeners();
// Later...
cleanup();React Integration
import { useEffect } from 'react';
import { DataSource } from 'bindra';
function UserList() {
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
const ds = new DataSource<User>({ url: '/api/users' });
// Subscribe to data changes
const handleFetch = (data: User[]) => setUsers(data);
ds.on('afterFetch', handleFetch);
// Fetch initial data
ds.fetch();
// Cleanup
return () => {
ds.off('afterFetch', handleFetch);
};
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Middleware with Events
const users = new DataSource<User>({ url: '/api/users' });
// Log all operations
users.on('beforeFetch', () => console.log('[FETCH] Starting...'));
users.on('afterFetch', (data) => console.log('[FETCH] Complete:', data.length));
users.on('beforeCreate', (record) => console.log('[CREATE] Starting...', record));
users.on('afterCreate', (record) => console.log('[CREATE] Complete:', record));
users.on('beforeUpdate', ({ id, changes }) =>
console.log('[UPDATE] Starting...', id, changes)
);
users.on('afterUpdate', (record) => console.log('[UPDATE] Complete:', record));
users.on('beforeDelete', (id) => console.log('[DELETE] Starting...', id));
users.on('afterDelete', (id) => console.log('[DELETE] Complete:', id));
// All operations are now logged
await users.fetch();
await users.create({ name: 'Alice' });
await users.update(1, { name: 'Bob' });
await users.delete(1);Real-time Events
const chat = new DataSource<Message>({
url: '/api/messages',
realtime: {
enabled: true,
url: 'wss://api.example.com/ws'
}
});
// Connection status
chat.on('realtime:connected', () => {
console.log('Connected to WebSocket');
showOnlineIndicator();
});
chat.on('realtime:disconnected', () => {
console.log('Disconnected from WebSocket');
showOfflineIndicator();
});
// Incoming messages
chat.on('realtime:message', (message) => {
console.log('New message:', message);
if (message.type === 'message.created') {
addMessageToUI(message.data);
playNotificationSound();
}
});
// Error handling
chat.on('error', (error) => {
console.error('Chat error:', error);
showErrorNotification(error.message);
});Custom Events
class MyDataSource<T> extends DataSource<T> {
async customOperation(data: T) {
// Emit custom event before
this.emit('beforeCustomOp', data);
try {
// Do something
const result = await this.processData(data);
// Emit custom event after
this.emit('afterCustomOp', result);
return result;
} catch (error) {
this.emit('error', error);
throw error;
}
}
}
const ds = new MyDataSource({ url: '/api/data' });
// Listen to custom events
ds.on('beforeCustomOp', (data) => {
console.log('Custom operation starting...');
});
ds.on('afterCustomOp', (result) => {
console.log('Custom operation complete:', result);
});Best Practices
1. Always Clean Up Listeners
// ✅ Good - Clean up in useEffect
useEffect(() => {
const handler = (data) => setData(data);
ds.on('afterFetch', handler);
return () => ds.off('afterFetch', handler);
}, []);
// ❌ Bad - Memory leak
useEffect(() => {
ds.on('afterFetch', (data) => setData(data));
// Never cleaned up
}, []);2. Use Named Functions for Handlers
// ✅ Good - Can be removed
const handleUpdate = (user) => console.log(user);
ds.on('afterUpdate', handleUpdate);
ds.off('afterUpdate', handleUpdate);
// ❌ Bad - Can't be removed
ds.on('afterUpdate', (user) => console.log(user));
// No reference to remove3. Use once() for Initialization
// ✅ Good - Fires once
ds.once('afterFetch', (data) => {
initializeApp(data);
});
// ❌ Bad - Fires every time
ds.on('afterFetch', (data) => {
initializeApp(data); // Called repeatedly
});4. Handle Errors Globally
// ✅ Good - Global error handler
ds.on('error', (error) => {
logError(error);
showErrorToUser(error.message);
});
// ❌ Bad - No error handling
// Errors go unhandled5. Keep Event Names Consistent
// ✅ Good - Consistent naming
ds.on('beforeFetch', handler);
ds.on('afterFetch', handler);
ds.on('beforeCreate', handler);
ds.on('afterCreate', handler);
// ❌ Bad - Inconsistent
ds.on('fetch-start', handler);
ds.on('fetchComplete', handler);
ds.on('creating', handler);
ds.on('created', handler);Related Documentation
- DataSource - DataSource lifecycle events
- Observable - Reactive primitives
- Getting Started - Event system basics