Computed & Effect (v16)
📚 Computed and Effect
computed() - Derived state that auto-updates:
- Memoized - only recalculates when dependencies change
- Read-only - cannot be set directly
- Lazy - only calculates when read
effect() - Side effects when signals change:
- Runs when tracked signals change
- Great for logging, localStorage, external APIs
- Auto-cleanup on destroy
🎯 Interview Questions
- Q1: What is the difference between computed() and effect()?
- A: computed() creates derived read-only values. effect() runs side effects when signals change but returns nothing.
- Q2: Is computed() lazy or eager?
- A: Lazy - it only calculates when the value is read, not when dependencies change.
- Q3: Can you write to signals inside effect()?
- A: By default no (to prevent infinite loops). Use allowSignalWrites: true or untracked() to do it safely.
- Q4: What is untracked()?
- A: Reads signal value without creating dependency. Useful in effects when you don't want to track certain signals.
🔧 Live Demo - computed()
Shopping Cart Example
Subtotal: $59.98(computed: price × quantity)
Tax (8%): $4.80(computed: subtotal × 0.08)
Total: $64.78(computed: subtotal + tax)
Filtered List (computed)
- Apple
- Banana
- Cherry
- Date
- Elderberry
- Fig
- Grape
Found: 7 of 7
🔧 Live Demo - effect()
Effect Logger
Theme: light
Effect Log (check console too):
- [12:07:42 PM] Theme changed to: light
💻 Computed & Effect Code
import { signal, computed, effect, untracked } from '@angular/core';
// ===== computed() - Derived State =====
const price = signal(10);
const quantity = signal(2);
// Computed automatically recalculates when dependencies change
const total = computed(() => price() * quantity());
console.log(total()); // 20
price.set(15);
console.log(total()); // 30 (auto-updated!)
// Computed is read-only
// total.set(100); // ❌ Error!
// ===== effect() - Side Effects =====
const theme = signal<'light' | 'dark'>('light');
// Effect runs when theme signal changes
effect(() => {
const currentTheme = theme();
console.log('Theme changed to:', currentTheme);
localStorage.setItem('theme', currentTheme);
document.body.className = currentTheme;
});
theme.set('dark'); // Effect runs automatically!
// ===== untracked() - Read Without Tracking =====
const firstName = signal('John');
const lastName = signal('Doe');
const logCount = signal(0);
effect(() => {
// This effect only tracks firstName
const first = firstName();
// Read lastName without tracking (won't re-run if lastName changes)
const last = untracked(() => lastName());
console.log(`Name: ${first} ${last}`);
});
// ===== Effect Cleanup =====
effect((onCleanup) => {
const interval = setInterval(() => {
console.log('Tick:', Date.now());
}, 1000);
// Cleanup when effect re-runs or component destroys
onCleanup(() => clearInterval(interval));
});
// ===== Effect Options =====
effect(() => { ... }, {
allowSignalWrites: true, // Allow writing to signals (careful!)
injector: this.injector, // Custom injector
});
// ===== Creating Effect Outside Constructor =====
// In a method (need injector)
const injector = inject(Injector);
someMethod() {
runInInjectionContext(this.injector, () => {
effect(() => {
console.log('This works!');
});
});
}