Angular Signals: The Complete Guide to Reactive State Management
Master Angular Signals with this beginner-friendly guide. Learn signal(), computed(), effect() with simple analogies, decision trees, and real-world examples like shopping carts and theme switchers.
What are Angular Signals?
Think of a Signal as a TV channel that always shows the current program. Anyone who tunes in can see what's playing right now. When the program changes, everyone watching sees the update instantly.
In technical terms, a Signal is a wrapper around a value that notifies Angular when that value changes. Unlike Observables (which are like mail delivery - you get values over time), Signals always have a current value you can read.
The Three Signal Primitives
1. signal() - The Value Holder
Analogy: šŗ A TV channel broadcasting a value. Anyone can tune in and see the current show.
// Create a signal with initial value
const count = signal(0);
// Read the value (call it like a function)
console.log(count()); // 0
// Update with set() - replace completely
count.set(5);
// Update with update() - transform current
count.update(current => current + 1);
Real-World Example: Shopping Cart Counter
@Component({
template: `<span>Cart: {{ cartCount() }} items</span>`
})
export class HeaderComponent {
cartCount = signal(0);
addToCart() {
this.cartCount.update(count => count + 1);
}
}
2. computed() - The Auto-Calculator
Analogy: š§® A spreadsheet cell with a formula. When cells it depends on change, it automatically recalculates.
const quantity = signal(2);
const price = signal(50);
// Auto-updates when quantity OR price changes
const total = computed(() => quantity() * price());
console.log(total()); // 100
quantity.set(3);
console.log(total()); // 150 (auto-updated!)
3. effect() - The Watcher
Analogy: š A doorbell notification. Whenever something changes, it triggers an action.
const theme = signal('light');
// Runs whenever theme changes
effect(() => {
document.body.className = theme();
localStorage.setItem('theme', theme());
});
theme.set('dark'); // Auto-saves!
Decision Tree: Which Primitive Should I Use?
Decision Tree: Signal vs Observable?
set() vs update(): When to Use Which?
Real-World Pattern: E-Commerce Cart
@Injectable({ providedIn: 'root' })
export class CartService {
// State
private items = signal<CartItem[]>([]);
// Computed values (auto-update)
readonly itemCount = computed(() =>
this.items().length
);
readonly subtotal = computed(() =>
this.items().reduce((sum, i) =>
sum + i.price * i.qty, 0)
);
readonly total = computed(() =>
this.subtotal() * 1.08 // with tax
);
// Actions
addItem(product: Product) {
this.items.update(items =>
[...items, { ...product, qty: 1 }]
);
}
}
Common Mistakes to Avoid
ā Don't forget parentheses in templates
<!-- Wrong --> <p>{{ count }}</p>
<!-- Right --> <p>{{ count() }}</p>
ā Don't use effect() for derived state
// Wrong - use computed instead!
effect(() => {
this.fullName = this.firstName() + ' ' + this.lastName();
});
// Right
fullName = computed(() =>
this.firstName() + ' ' + this.lastName()
);
Summary Cheat Sheet
| Primitive | Purpose | Analogy | Use Case |
|---|---|---|---|
| signal() | Store mutable value | šŗ TV Channel | Cart count |
| computed() | Derive from signals | š§® Spreadsheet | Total price |
| effect() | Side effects | š Doorbell | Auto-save |
Quick Decision:
- Store something? ā
signal() - Calculate something? ā
computed() - DO something on change? ā
effect()