skip to content
BKB
Table of Contents

Our service works, but every consumer can currently change its signal. We will protect writes, calculate useful totals, and save topics between refreshes.

Three signal tools

  • signal() stores writable state.
  • computed() derives a value from other signals.
  • effect() performs an external side effect when a signal changes.

1. Protect the state

private readonly topicsState = signal<TopicItem[]>(initialTopics);
readonly topics = this.topicsState.asReadonly();

Components can read topics, but only service methods can update topicsState. This creates one clear place for business rules.

2. Calculate totals

readonly completedCount = computed(
() => this.topicsState().filter(topic => topic.completed).length
);
readonly pendingCount = computed(
() => this.topicsState().filter(topic => !topic.completed).length
);
Why not use an effect to calculate these totals?

The totals are derived state. computed() expresses that relationship directly and always stays synchronized. Effects are for work outside the signal graph.

3. Save to local storage safely

This project uses SSR, where localStorage does not exist. Wait until Angular is running in the browser:

private readonly storageReady = signal(false);
constructor() {
afterNextRender(() => {
const savedTopics = localStorage.getItem('topics');
if (savedTopics) {
try {
this.topicsState.set(JSON.parse(savedTopics) as TopicItem[]);
} catch {
localStorage.removeItem('topics');
}
}
this.storageReady.set(true);
});
effect(() => {
const topics = this.topicsState();
if (this.storageReady()) {
localStorage.setItem('topics', JSON.stringify(topics));
}
});
}

The readiness flag prevents the save effect from overwriting existing storage before it has been loaded.

Checkpoint

Toggle a topic, refresh the page, and confirm the value remains. Then inspect Local Storage in browser developer tools.

Small challenge

Handle valid JSON with the wrong shape. For example, decide what should happen if storage contains {} instead of an array.

Navigation: Previous: Services · Series overview · Next: Reactive forms