Notification System
A complete notification system with auto-dismiss, multiple types, animations, and queue management.
Features Demonstrated
Dependency Injection - Singleton service pattern
Event Aggregator - Global notification triggering
Animations - CSS transitions for enter/leave
Timers - Auto-dismiss with setTimeout
Array manipulation - Add/remove notifications
Dynamic CSS classes - Type-based styling
Conditional rendering - Show/hide based on array length
Code
Service (notification-service.ts)
// src/services/notification-service.ts
import { DI } from '@aurelia/kernel';
export interface Notification {
id: string;
type: 'success' | 'error' | 'warning' | 'info';
title: string;
message: string;
duration: number; // milliseconds, 0 = no auto-dismiss
dismissible: boolean;
timestamp: Date;
expiresAt?: number;
remaining?: number;
}
export const INotificationService = DI.createInterface<INotificationService>(
'INotificationService',
x => x.singleton(NotificationService)
);
export interface INotificationService {
readonly notifications: Notification[];
show(options: Partial<Notification>): string;
success(title: string, message: string, duration?: number): string;
error(title: string, message: string, duration?: number): string;
warning(title: string, message: string, duration?: number): string;
info(title: string, message: string, duration?: number): string;
dismiss(id: string): void;
clear(): void;
}
class NotificationService implements INotificationService {
notifications: Notification[] = [];
private nextId = 1;
private timers = new Map<string, number>();
private progressTimers = new Map<string, number>();
show(options: Partial<Notification>): string {
const notification: Notification = {
id: `notification-${this.nextId++}`,
type: options.type || 'info',
title: options.title || '',
message: options.message || '',
duration: options.duration !== undefined ? options.duration : 5000,
dismissible: options.dismissible !== undefined ? options.dismissible : true,
timestamp: new Date(),
remaining: options.duration ?? 5000,
expiresAt: options.duration ? Date.now() + options.duration : undefined
};
// Add to beginning of array (newest first)
this.notifications.unshift(notification);
// Auto-dismiss if duration > 0
if (notification.duration > 0) {
const timer = window.setTimeout(() => {
this.dismiss(notification.id);
}, notification.duration);
this.timers.set(notification.id, timer);
const progress = window.setInterval(() => {
if (!notification.expiresAt) return;
const remaining = Math.max(notification.expiresAt - Date.now(), 0);
notification.remaining = remaining;
if (remaining <= 0) {
window.clearInterval(progress);
this.progressTimers.delete(notification.id);
}
}, 100);
this.progressTimers.set(notification.id, progress);
}
return notification.id;
}
success(title: string, message: string, duration = 5000): string {
return this.show({ type: 'success', title, message, duration });
}
error(title: string, message: string, duration = 0): string {
// Errors don't auto-dismiss by default
return this.show({ type: 'error', title, message, duration });
}
warning(title: string, message: string, duration = 7000): string {
return this.show({ type: 'warning', title, message, duration });
}
info(title: string, message: string, duration = 5000): string {
return this.show({ type: 'info', title, message, duration });
}
dismiss(id: string): void {
// Clear timer if exists
const timer = this.timers.get(id);
if (timer) {
clearTimeout(timer);
this.timers.delete(id);
}
const progress = this.progressTimers.get(id);
if (progress) {
clearInterval(progress);
this.progressTimers.delete(id);
}
// Remove notification
const index = this.notifications.findIndex(n => n.id === id);
if (index !== -1) {
this.notifications.splice(index, 1);
}
}
clear(): void {
// Clear all timers
this.timers.forEach(timer => clearTimeout(timer));
this.timers.clear();
this.progressTimers.forEach(interval => clearInterval(interval));
this.progressTimers.clear();
// Clear all notifications
this.notifications = [];
}
}Component (notification-container.ts)
Template (notification-container.html)
Styles (notification-container.css)
Registration (main.ts)
Usage in Root Component (my-app.html)
Usage in Any Component
How It Works
Singleton Service Pattern
The INotificationService is registered as a singleton, so the same instance is shared across the entire application. Any component can inject it and trigger notifications.
Auto-Dismiss Timer
When a notification is added with duration > 0, a timer is created that automatically dismisses it after the specified time. The timer is stored in a Map so it can be cleared if the user manually dismisses the notification.
Reactive Array
The notifications array is a reactive property. When notifications are added or removed, Aurelia's binding system automatically updates the DOM.
Progress Bar Animation
The progress bar uses a computed property (getProgressWidth) that calculates the percentage remaining based on elapsed time. This creates a smooth countdown animation.
Variations
Stacking vs Replacing
Current implementation stacks notifications. For "replacing" behavior (only show one at a time):
Position Options
Make position configurable:
Action Buttons
Add action buttons to notifications:
Pause on Hover
Pause the auto-dismiss timer when hovering:
Related
Dependency Injection - Singleton services
Event Aggregator - Alternative global communication
Conditional Rendering -
if.binddocumentationList Rendering -
repeat.fordocumentation
Last updated
Was this helpful?