Now that you've learned querying child elements with signal queries, let's explore how to react to signal changes with effects. Effects are functions that run automatically when their dependencies change, making them perfect for side effects like logging, DOM manipulation, or API calls.
Important: Effects should be the last API you reach for. Always prefer computed() for derived values and linkedSignal() for values that can be both derived and manually set. If you find yourself copying data from one signal to another with an effect, it's a sign you should move your source-of-truth higher up and use computed() or linkedSignal() instead. Effects are best for syncing signal state to imperative, non-signal APIs.
In this activity, you'll learn how to use the effect() function appropriately for legitimate side effects that respond to signal changes.
You have a theme manager app with signals already set up. Now you'll add effects to automatically react to signal changes.
-
Import effect function
Add
effectto your existing imports.// Add effect to existing importsimport {Component, signal, computed, effect, ChangeDetectionStrategy} from '@angular/core';The
effectfunction creates a reactive side effect that runs automatically when any signals it reads change. -
Create an effect for local storage
Add an effect that automatically saves the theme to local storage when it changes.
constructor() { // Save theme to localStorage whenever it changes effect(() => { localStorage.setItem('theme', this.theme()); console.log('Theme saved to localStorage:', this.theme()); });}This effect runs whenever the theme signal changes, automatically persisting the user's preference.
-
Create an effect for logging user activity
Add an effect that logs when the user logs in or out.
constructor() { // ... previous effect // Log user activity changes effect(() => { const status = this.isLoggedIn() ? 'logged in' : 'logged out'; const user = this.username(); console.log(`User ${user} is ${status}`); });}This effect demonstrates how effects can read multiple signals and react to changes in any of them.
-
Create an effect with cleanup
Add an effect that sets up a timer and cleans up when the component is destroyed.
constructor() { // ... previous effects // Timer effect with cleanup effect((onCleanup) => { const interval = setInterval(() => { console.log('Timer tick - Current theme:', this.theme()); }, 5000); // Clean up the interval when the effect is destroyed onCleanup(() => { clearInterval(interval); console.log('Timer cleaned up'); }); });}This effect demonstrates how to clean up resources when effects are destroyed or re-run.
-
Test the effects
Open the browser console and interact with the app:
- Toggle Theme - See localStorage saves and timer logs
- Login/Logout - See user activity logging
- Watch Timer - See periodic theme logging every 5 seconds
The effects run automatically whenever their tracked signals change!
Excellent! You've now learned how to use effects with signals. Key concepts to remember:
- Effects are reactive: They automatically run when any signal they read changes
- Side effects only: Perfect for logging, DOM manipulation, API calls, and syncing to imperative APIs
- Cleanup: Use the
onCleanupcallback to clean up resources like timers or subscriptions - Automatic tracking: Effects automatically track which signals they read and re-run when those signals change
Remember: Use effects sparingly! The examples in this lesson (localStorage sync, logging, timers) are appropriate uses. Avoid effects for:
- Deriving values from other signals - use
computed()instead - Creating writable derived state - use
linkedSignal()instead - Copying data between signals - restructure to use a shared source of truth
Effects are powerful but should be your last resort when computed() and linkedSignal() can't solve your use case.