typescript 70 lines · 8 steps

Building a signal-based toast service in Angular

An injectable Angular service manages transient notifications with signals and auto-dismiss timers.

Explained by highlit
1import { Injectable, signal, computed } from '@angular/core';
2 
3export type ToastKind = 'success' | 'error' | 'info' | 'warning';
4 
5export interface Toast {
6 id: number;
7 kind: ToastKind;
8 message: string;
9 title?: string;
10 duration: number;
11}
12 
13interface ToastOptions {
14 title?: string;
15 duration?: number;
16}
17 
18@Injectable({ providedIn: 'root' })
19export class ToastService {
20 private readonly _toasts = signal<Toast[]>([]);
21 private readonly timers = new Map<number, ReturnType<typeof setTimeout>>();
22 private nextId = 0;
23 
24 readonly toasts = this._toasts.asReadonly();
25 readonly hasToasts = computed(() => this._toasts().length > 0);
26 
27 success(message: string, options?: ToastOptions) {
28 return this.show('success', message, options);
29 }
30 
31 error(message: string, options?: ToastOptions) {
32 return this.show('error', message, { duration: 8000, ...options });
33 }
34 
35 info(message: string, options?: ToastOptions) {
36 return this.show('info', message, options);
37 }
38 
39 warning(message: string, options?: ToastOptions) {
40 return this.show('warning', message, options);
41 }
42 
43 dismiss(id: number) {
44 const timer = this.timers.get(id);
45 if (timer) {
46 clearTimeout(timer);
47 this.timers.delete(id);
48 }
49 this._toasts.update((list) => list.filter((t) => t.id !== id));
50 }
51 
52 clear() {
53 this.timers.forEach(clearTimeout);
54 this.timers.clear();
55 this._toasts.set([]);
56 }
57 
58 private show(kind: ToastKind, message: string, options?: ToastOptions): number {
59 const id = ++this.nextId;
60 const duration = options?.duration ?? 4000;
61 const toast: Toast = { id, kind, message, title: options?.title, duration };
62 
63 this._toasts.update((list) => [...list, toast]);
64 
65 if (duration > 0) {
66 this.timers.set(id, setTimeout(() => this.dismiss(id), duration));
67 }
68 return id;
69 }
70}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A private writable signal paired with a public read-only view keeps mutation controlled inside the service.
  2. 2Convenience methods that delegate to one private core keep behavior consistent while defaults vary per kind.
  3. 3Tracking timers in a Map lets you cancel scheduled dismissals precisely when a toast is removed early.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a signal-based toast service in Angular — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code