What are Angular signals?
Angular Signals are a new reactivity primitive introduced in Angular to manage state changes in a more explicit, granular, and performant way. They represent a value that can change over time, notifying interested consumers when an update occurs. This modern approach aims to simplify state management and improve change detection mechanisms within Angular applications.
What are Signals?
Signals are zero-argument functions that return their current value. When a signal's value changes, it automatically notifies any computations or effects that depend on it. This push-based change detection system allows Angular to update only the specific parts of the UI that are affected by a change, rather than re-checking the entire component tree.
- Granular Reactivity: Signals track dependencies automatically, enabling highly optimized updates.
- Value-Based: They encapsulate a single value, making state changes explicit and predictable.
- Push-Based: Consumers are notified when a signal's value changes, leading to efficient updates.
Core Signal Concepts
signal()
The signal() function creates a writable signal. You can update its value using the .set() method or the .update() method for more complex transformations based on the current value.
import { signal } from '@angular/core';
const count = signal(0);
console.log(count()); // Output: 0
count.set(5);
console.log(count()); // Output: 5
count.update(currentCount => currentCount + 1);
console.log(count()); // Output: 6
computed()
computed() creates a read-only signal that derives its value from one or more other signals. It automatically re-evaluates only when its dependencies change, and its value is cached until then, making it efficient for derived state.
import { signal, computed } from '@angular/core';
const firstName = signal('John');
const lastName = signal('Doe');
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // Output: John Doe
firstName.set('Jane');
console.log(fullName()); // Output: Jane Doe
effect()
effect() registers a side effect that will run whenever one of its signal dependencies changes. Effects are useful for synchronizing signal state with the DOM, logging, or other non-signal-based APIs. They are typically used for rendering or debugging and should generally be avoided for managing core application state.
import { signal, effect } from '@angular/core';
const message = signal('Hello');
effect(() => {
console.log(`Current message: ${message()}`);
});
message.set('World'); // Output: Current message: World (due to effect)
Benefits of Using Signals
- Improved Performance: More granular change detection reduces the amount of work Angular needs to do on each update.
- Simplified State Management: State changes become more explicit and easier to reason about.
- Better Developer Experience: Provides a clear and consistent pattern for reactivity, reducing reliance on NgZone and Zone.js for many use cases.
- Future-Proofing: Aligns Angular with modern reactivity patterns seen in other frameworks, paving the way for further optimizations.
Integration with Components
Signals can be seamlessly integrated into Angular components. You can declare signals as component properties, use them in templates, and update them based on user interactions or data fetched from services. Signals naturally fit into component lifecycle and data flow, providing a powerful tool for reactive UI updates without needing to manually subscribe/unsubscribe from Observables for simple state.
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count() }}</p>
<button (click)="increment()">Increment</button>
`,
standalone: true
})
export class CounterComponent {
count = signal(0);
increment() {
this.count.update(currentCount => currentCount + 1);
}
}