While Writable Signals hold data and Computed Signals transform data, an Effect is the "performer".
1. What is an Effect?
An Effect is a function that runs automatically whenever the signals it uses change. Unlike the other two types of signals, an effect doesn't return a value. Instead, it performs a task.
Think of it as a loyal assistant watching your signals. The moment a signal changes, the assistant jumps up and executes the specific job you gave them—like saving a file, updating a chart, or logging a message.
2. Basic Syntax
You define an effect using the effect() function, usually inside a component's constructor.
import { Component, signal, effect } from '@angular/core';
@Component({...})
export class UserProfile {
username = signal('Harry');
constructor() {
// This effect runs immediately once, then every time 'username' changes
effect(() => {
console.log(`The username is now: ${this.username()}`);
});
}
}
3. What can you do with Effects?
Effects are designed for "side effects"—actions that need to happen because data changed, but aren't part of the UI rendering itself. Can use async functions as well
// async code
effect( async () => await service.set(x) )
// side effects
effect(() => storage.save() )
Effects trigger only once after all the dependent signals are updated(even when updated in multiple places in the code). It will trigger once when the thread is ideal and empty.
Effects are asynchronous and batched
When you update a signal, the effect is "scheduled" to run. It doesn't jump the line and execute immediately. Instead, it waits for the current Microtask to finish.
Think of it like a waiter at a restaurant: if three people at the table order water at different times during the conversation, the waiter doesn't run to the kitchen three times. They wait for you to finish talking, take the whole order, and then make one trip.
count = signal(0);
constructor() {
effect(() => {
console.log('Effect ran with:', this.count());
});
}
updateMultipleTimes() {
this.count.set(1);
this.count.set(2);
this.count.set(3);
// The console will NOT show 1 and 2.
// It will wait until this function is DONE, then log "3".
}
Angular batches updates naturally during any code execution (like an event handler or an HTTP response).
// Even if you update two completely different signals
updateBoth() {
this.firstName.set('Jane');
this.lastName.set('Doe');
}
// An effect listening to both
// will only trigger ONCE after updateBoth() finishes.
Common use cases include:
Logging/Analytics: Sending data to a server when a user reaches a certain milestone.
External APIs: Manually manipulating a non-Angular library (like a D3.js chart or a Google Map).
LocalStorage: Saving the state of a form so the user doesn't lose progress if they refresh.
Custom Notifications: Triggering a "toast" message or a sound effect.
4. The Rules of Effects
To keep your app stable, Angular enforces a few "safety rails" for effects:
No Signal Writes (By Default)
You cannot update a signal inside an effect.
Why? If Signal A triggers an Effect that updates Signal A, you create an infinite loop that crashes your browser.
Exception: If you absolutely must, you can enable
allowSignalWrites: true, but it's generally a sign that you should be using acomputedsignal instead.
Injection Context
Effects must be created where Angular has access to "injection context" (usually the constructor or as a class member). If you try to create an effect inside a random method like onClick(), it will fail unless you manually pass it a reference to the app's injector.
5. Cleaning Up (The onCleanup function)
Sometimes an effect starts a task that needs to be stopped before the next run (like a timer or a network request). Effects give you a onCleanup function for this:
effect((onCleanup) => {
const timer = setInterval(() => {
console.log('Ping!');
}, 1000);
// This runs right before the effect re-runs or the component is destroyed
onCleanup(() => {
clearInterval(timer);
});
});
Code Playground
Think of effect as your bridge to the outside world. It’s the designated spot for "side effects"—like logging or API syncing—that should run automatically and efficiently in the background whenever your state changes.
Thanks for reading. Happy coding!!!
Top comments (0)