ARTICLE AD BOX
It might be due to Zoneless Change detection, check if you have provideZonelessChangeDetection set in main.ts.
Angular 21 marked zoneless change detection as stable and basically, the property updates will not trigger change detection (like how it worked pre zoneless) so if you have a property that requires change detection to be fired, we must be a signal, you can also use markForCheck or detectChanges of ChangeDetectorRef.
It is important to note that a signal marks a change when the actual value has changed for primitives (string, number, etc.) and memory reference change for non primitives (array, object, etc).
To solve it, just convert the toasts array to a signal array, so that when we update change detection picks this up and updates the UI.
// toast.service.ts import { Injectable } from '@angular/core'; export interface Toast { message: string; duration: number; type: 'success' | 'error' }; @Injectable({ providedIn: 'root', }) export class ToastService { toasts = signal<Toast[]>([]); add(message: string, duration: number = 3000, type: 'success' | 'error' = 'success') { this.toasts.update((toastPrev: Toast[]) => ([...toastPrev, { message, duration, type }])); setTimeout(() => this.remove(0), duration); } remove(index: number) { this.toasts.update((toastPrev: Toast[]) => { // arrays and objects are stored as memory references toastPrev.splice(index, 1); // toastPrev will contain the same reference after splicing. return [...toastPrev]; // new array instance must be returned for non primitive signals, so that memory reference is different which marks the signal as changed. }); } }