A read-only signal is a public Signal view created from a private WritableSignal with asReadonly(). It exposes the current value and supports computed signals, while preventing callers from changing the value directly. section 6.2.1 section 10.4.4
Use the pattern to keep signal changes inside the class or service that owns the state:
- Store mutable state in a private WritableSignal. section 10.4.4
- Expose a getter or property returning writableSignal.asReadonly(). section 6.2.1 section 20.3.1
- Change the state through deliberate class or service methods using set, update, or mutate. section 6.2.1 section 20.4.1
- Let consumers read the signal and derive computed values without receiving direct write access. section 10.4.4
Protecting cart state
private linesSignal = signal<CartLine[]>([]);\n\nget lines(): Signal<CartLine[]> {\n return this.linesSignal.asReadonly();\n}\n\naddLine(product: Product, quantity = 1) {\n this.linesSignal.mutate(lines => {\n // add the selection\n });\n}\n\nConsumers can read lines() or build computed signals from it, but must use Cart methods to add or remove selections. section 6.2.1
Listing 20.10. The contents of the message.service.ts file in the src/app/messages folder
import { Injectable, signal } from "@angular/core";
import { Message } from "./message.model";
@Injectable()
export class MessageService {
private writableMessages = signal<Message[]>([]);
messages = this.writableMessages.asReadonly();
reportMessage(msg: Message) {
this.writableMessages.mutate(msgs => msgs.push(msg));
}
}
Shows a complete Angular service that stores messages in a private signal, exposes messages as read-only, and appends through reportMessage(). section 20.3.1
