Computed Signals β‘
Computed signals are like smart calculators that automatically update their results whenever their inputs change. Think of them as Excel formulas - when you change a cell, all dependent formulas update automatically!
π― What are Computed Signals?
Section titled βπ― What are Computed Signals?βComputed signals are read-only signals that derive their value from other signals. They automatically recalculate when their dependencies change, giving you efficient reactive derived state.
const firstName = signal('John');const lastName = signal('Doe');
// β
Computed signal automatically updatesconst fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // "John Doe"
firstName.set('Jane');console.log(fullName()); // "Jane Doe" - automatically updated!π Real-World Examples
Section titled βπ Real-World Examplesβ1. Shopping Cart Totals
Section titled β1. Shopping Cart Totalsβ@Component({ template: ` <div> <p>Items: {{ items().length }}</p> <p>Total: {{ total() | currency }}</p> <p>Tax: {{ tax() | currency }}</p> <p>Final: {{ finalTotal() | currency }}</p> </div> `})export class CartComponent { items = signal<CartItem[]>([ { name: 'Laptop', price: 999, quantity: 1 }, { name: 'Mouse', price: 29, quantity: 2 } ]);
// β
All computed signals update automatically total = computed(() => this.items().reduce((sum, item) => sum + (item.price * item.quantity), 0) );
tax = computed(() => this.total() * 0.08);
finalTotal = computed(() => this.total() + this.tax());}2. User Profile Status
Section titled β2. User Profile Statusβ@Component({ template: ` <div> <h3>{{ user().name }}</h3> <p class="{{ statusClass() }}">{{ statusMessage() }}</p> <div class="progress-bar"> <div [style.width.%]="completionPercentage()"></div> </div> </div> `})export class ProfileComponent { user = signal({ name: 'John Doe', email: 'john@example.com', avatar: '', bio: '', phone: '555-1234' });
// β
Computed signals for derived state completionPercentage = computed(() => { const user = this.user(); const fields = [user.name, user.email, user.avatar, user.bio, user.phone]; const completed = fields.filter(field => field && field.trim()).length; return Math.round((completed / fields.length) * 100); });
statusMessage = computed(() => { const percentage = this.completionPercentage(); if (percentage === 100) return 'Profile Complete! π'; if (percentage >= 75) return 'Almost done! π'; if (percentage >= 50) return 'Making progress... π'; return 'Just getting started π'; });
statusClass = computed(() => { const percentage = this.completionPercentage(); if (percentage === 100) return 'status-complete'; if (percentage >= 75) return 'status-good'; if (percentage >= 50) return 'status-progress'; return 'status-start'; });}3. Search & Filter
Section titled β3. Search & Filterβ@Component({ template: ` <div> <input [value]="searchTerm()" (input)="updateSearch($event)" /> <select [value]="categoryFilter()" (change)="updateCategory($event)"> <option value="">All Categories</option> <option value="tech">Tech</option> <option value="books">Books</option> </select>
<p>Showing {{ filteredItems().length }} of {{ allItems().length }}</p>
@for (item of filteredItems(); track item.id) { <div class="item">{{ item.name }}</div> } </div> `})export class SearchComponent { searchTerm = signal(''); categoryFilter = signal('');
allItems = signal([ { id: 1, name: 'MacBook Pro', category: 'tech' }, { id: 2, name: 'JavaScript Book', category: 'books' }, { id: 3, name: 'iPhone', category: 'tech' } ]);
// β
Computed signal handles complex filtering filteredItems = computed(() => { const items = this.allItems(); const search = this.searchTerm().toLowerCase(); const category = this.categoryFilter();
return items.filter(item => { const matchesSearch = !search || item.name.toLowerCase().includes(search); const matchesCategory = !category || item.category === category; return matchesSearch && matchesCategory; }); });
updateSearch(event: Event) { this.searchTerm.set((event.target as HTMLInputElement).value); }
updateCategory(event: Event) { this.categoryFilter.set((event.target as HTMLSelectElement).value); }}β Best Practices
Section titled ββ Best Practicesβ1. Keep Computations Pure
Section titled β1. Keep Computations Pureβ// β
Good - Pure function, no side effectsconst total = computed(() => this.items().reduce((sum, item) => sum + item.price, 0));
// β Avoid - Side effects in computed signalsconst badComputed = computed(() => { console.log('Computing...'); // Side effect! return this.items().length;});2. Use for Derived State
Section titled β2. Use for Derived Stateβ// β
Good - Perfect for derived stateconst isFormValid = computed(() => this.email().includes('@') && this.password().length >= 8);
// β Avoid - Use regular signals for independent stateconst currentTime = computed(() => Date.now()); // This won't update!3. Chain Computed Signals
Section titled β3. Chain Computed Signalsβ// β
Good - Chain computations for complex logicconst subtotal = computed(() => this.items().reduce((sum, item) => sum + item.price, 0));
const tax = computed(() => this.subtotal() * 0.08);
const total = computed(() => this.subtotal() + this.tax());π― Quick Checklist
Section titled βπ― Quick Checklistβ- Use computed signals for derived state that depends on other signals
- Keep computation functions pure (no side effects)
- Chain computed signals for complex calculations
- Use computed signals in templates for reactive UI updates
- Prefer computed over manual calculations in effects
- Test computed signals by testing their inputs and outputs
π Next Steps
Section titled βπ Next Stepsβ- Effects API - Handle side effects with signals
- Linked Signals - Advanced signal patterns
- Control Flow - New template syntax with signals
Remember: Use computed signals to move complex logic out of templates and into reactive, testable functions. Theyβre perfect for derived state that needs to stay in sync! β‘