Signals are a reactivity primitive introduced in Angular to make state management more declarative, efficient, and explicit. They provide a new way to handle and react to changes in data, much like observables but with synchronous and fine-grained reactivity, ideal for template-driven UIs.
Introduced in Angular 16, Signals aim to replace some use-cases of RxJS (like BehaviorSubject) in component state management by providing a simpler and zone-less reactivity model.
Conceptual Understanding
A value is held inside a container that is referred to as a Signal:
- Tracks its dependencies automatically when read.
- Notifies consumers when it is updated.
- Can be used directly in templates and logic without subscriptions or manual unsubscriptions.
Analogy
A signal behaves how a reactive variable does:
- Reading it establishes a dependency.
- Updating it notifies everything that depends on it.
Types of Signals in Angular
Angular provides three primary ways to work with signals:
Type | Description |
signal() | The basic writable signal. Used for storing values. |
computed() | A derived signal that computes values from other signals. |
effect() | A side-effect that runs automatically when dependent signals change. |
Signal API Overview
Writable Signal — signal()
import { signal } from '@angular/core';
const count = signal(0); // Creates a signal with initial value 0
count(); // GET: returns 0
count.set(10); // SET: changes value to 10
count.update(v => v + 1); // Increments count
Computed Signal — computed()
import { computed } from '@angular/core';
const count = signal(2);
const double = computed(() => count() * 2);
console.log(double()); // 4
count.set(5);
console.log(double()); // 10 (automatically recalculates)
- Automatically recalculates when dependencies change.
- Pure function, no side-effects allowed inside.
Effect — effect()
import { effect } from '@angular/core';
const count = signal(0);
effect(() => { console.log(`Count changed to: ${count()}`);
});
count.set(5); // Automatically triggers the effect
- Use for side-effects: console logs, HTTP calls, DOM manipulation, etc.
- Automatically runs when dependent signal changes.
Real-life Example: Counter Component with Signals
import { Component, signal, computed, effect } from '@angular/core';
@Component({ selector: 'app-counter', template: ` <h2>Count: {{ count() }}</h2> <h3>Double: {{ doubleCount() }}</h3> <button (click)="increment()">Increment</button> `
})
export class CounterComponent { count = signal(0); // Writable signal doubleCount = computed(() => this.count() * 2); // Derived signal constructor() { effect(() => { console.log('Count updated to:', this.count()); }); } increment() { this.count.update(c => c + 1); }
}
Key Benefits of Signals
Feature | Description |
Synchronous | Updates happen immediately (no need to subscribe/pipe). |
Fine-grained | Only dependent parts re-render. |
No Zones | Works without NgZone (good for performance). |
Simple API | Easier learning curve than RxJS. |
Built-in Cleanup | effect() handles cleanup on its own. |
Signals vs Observables (RxJS)
Feature | Signals | Observables |
Push/Pull | Pull-based | Push-based |
Async | Mostly sync | Fully async |
Simplicity | Easier API | More powerful but complex |
Cleanup | Auto-cleanup | Manual unsubscribe |
Best For | UI State | Streams, async data |
Use Cases
- Local component state (counter, toggles, forms)
- Derived data in templates
- ViewModel composition
- Replacing BehaviorSubject in components
Angular Signal Lifecycle & Change Detection
- Signals track where they’re used.
- Change detection is triggered only where needed (fine-grained).
- This allows zone-less rendering and better performance.
Conclusion
Angular Signals offer a simpler, more efficient way to manage UI state with fine-grained reactivity and no need for zones. With built-in dependency tracking and a clean API, they streamline component logic and performance. To leverage these cutting-edge features, it’s wise to hire Angular developer experts familiar with Signals and modern Angular best practices.