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

typescript
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps

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