Angular Signals for Reactive State
frontend
TypeScript
architecture
strict_senior
Modern state management using Angular Signals with examples and best practices.
By chris_d
12/8/2025
Prompt
Angular Signals State Management
Implement reactive state management for [Application] using Angular Signals.
Requirements
1. State Structure
Define signals for:
- [State 1] - [Data type] (e.g., user data)
- [State 2] - [Data type] (e.g., form state)
- [State 3] - [Data type] (e.g., UI state)
2. Signal Types to Implement
Writable Signals
For mutable state:
- User inputs
- Entity data
- Configuration values
- UI toggles
Computed Signals
For derived values:
- Filtered lists
- Aggregated totals
- Formatted displays
- Validation states
Effects
For side effects:
- API calls on state changes
- LocalStorage sync
- Logging
- Analytics tracking
3. Component Implementation
Create components using:
- Signal-based reactive state
- Computed values for derived data
- Effects for side effects
- Proper TypeScript typing
4. Service Layer
Implement services with:
- Private writable signals
- Public readonly signals
- Computed values
- Update methods
- Async data loading
5. Benefits Over Zone.js
Leverage:
- Fine-grained reactivity (only affected parts update)
- Better performance
- Automatic dependency tracking
- Simpler mental model
- OnPush change detection friendly
Implementation Pattern
import { Component, Injectable, signal, computed, effect } from '@angular/core'
// Component with signals
@Component({
selector: 'app-[name]',
template: `
<div>
<p>{{ [signalName]() }}</p>
<p>Computed: {{ [computedName]() }}</p>
<button (click)="update[Action]()">Update</button>
</div>
`
})
export class [ComponentName] {
// Writable signal
[signalName] = signal<[Type]>([initialValue])
// Computed signal
[computedName] = computed(() => {
return /* derive from other signals */
})
constructor() {
// Effect for side effects
effect(() => {
const value = this.[signalName]()
// React to changes
})
}
update[Action]() {
this.[signalName].set([newValue])
// or
this.[signalName].update(current => /* transform */)
}
}
// Service with signals
@Injectable({ providedIn: 'root' })
export class [ServiceName] {
// Private writable signal
private [dataSignal] = signal<[Type][]>([])
// Public readonly access
[data] = this.[dataSignal].asReadonly()
// Computed values
[computed] = computed(() =>
this.[dataSignal]().filter(/* condition */)
)
async load[Data]() {
const data = await this.api.get[Data]()
this.[dataSignal].set(data)
}
add[Item](item: [Type]) {
this.[dataSignal].update(items => [...items, item])
}
update[Item](id: string, updates: Partial<[Type]>) {
this.[dataSignal].update(items =>
items.map(item => item.id === id ? { ...item, ...updates } : item)
)
}
remove[Item](id: string) {
this.[dataSignal].update(items =>
items.filter(item => item.id !== id)
)
}
}
Best Practices
- Use signals for reactive state
- Keep signals focused and single-purpose
- Use computed() for derived values
- Use effect() for side effects only
- Make service signals readonly when exposing
- Leverage TypeScript for type safety
- Use update() for transformations
- Use set() for replacements
Tags
angular
signals
reactive
state-management
Tested Models
gpt-4
claude-3-opus