Angular has relied on its Change Detection mechanism and Observables for years to manage reactivity. A more precise, reliable, and effective method of handling state updates was made possible with the introduction of Signals, a new reactive primitive with Angular 16. Signals represent a major advancement in Angular's reactivity model, with the goal of streamlining state management while enhancing rendering speed and application effectiveness in general.

Signals: What Are They?
When a value changes, a signal, which is a reactive container, automatically alerts its dependents. Signals function synchronously and offer more transparent state tracking than Observables, which are push-based and usually asynchronous.
Key Characteristics
- Lightweight: They introduce very little overhead during creation and updates.
- Fine-grained: Only the components or computations that depend on the changed value are updated, rather than triggering a full view refresh.
- Predictable: State changes propagate immediately and in a consistent manner.
- Compatible: They integrate smoothly with existing Angular features and APIs.
Why Angular Introduced Signals?
Before Signals, Angular’s reactivity model leaned on:
- Zone.js-driven change detection
- @Input() property bindings
- RxJS Observables
- Manual state management techniques
While these tools were powerful, they came with notable drawbacks:
- Entire component trees could re-render unnecessarily
- Debugging reactive flows was often complicated
- Performance tuning demanded deep expertise in change detection strategies
Signals solve these challenges by introducing fine-grained reactivity. Instead of re-rendering broadly, Angular now updates only the parts of the application that directly depend on the changed state.
Signal Types
1. Signals That Can Be Written
Writable signals provide an API that lets you directly change their values. The signal function is called and the starting value is passed in as an argument to create them.Example:
Example:
import { signal } from '@angular/core';
const counter = signal(0);
counter.set(1); // updates value
counter.update(value => value + 1); // increments
Core Methods
| Method Name | Description |
|
set(newValue)
|
The set() method replaces the current value of the signal with a new one.
|
|
update(fn)
|
The update() method modifies the signal’s value based on its current state.
|
|
asReadonly()
|
The asReadonly() method creates a read-only version of the signal.
|
2. Computed Signals
Computed signals are reactive values that are automatically derived from one or more other signals. Instead of holding independent state, they act as pure functions of existing signals, recalculating only when their dependencies change.
Example:
import { signal, computed } from '@angular/core';
const price = signal(200);
const quantity = signal(4);
const total = computed(() => price() * quantity());
console.log(total()); // 800
If price or quantity changes, total is recalculated automatically. You don’t need to manually trigger updates—Angular handles the dependency tracking for you.
Important Behaviors
- Lazy evaluation: Computed signals don’t run until they’re accessed.
- Caching: Results are stored, so repeated reads don’t cause unnecessary recomputation.
- Dependency awareness: They re-run only when one of their input signals changes.
Why They’re Useful?
- Simplify derived state (e.g., totals, filters, formatted values).
- Reduce boilerplate compared to manual subscriptions or RxJS operators.
- Improve performance by avoiding redundant recalculations.
- Make reactive flows more predictable and easier to debug.
3. Effects
Effects are reactive functions that automatically run whenever the signals they depend on change. They’re designed to handle side effects—operations that go beyond pure state updates.
Example:
import { effect } from '@angular/core';
effect(() => {
console.log('Total changed:', total());
});
In this example, whenever total() changes, the effect executes and logs the new value.
Common Use Cases
- Effects are ideal for tasks that need to respond to state changes but don’t belong in core business logic:
- Logging application state changes
- Triggering API calls when certain values update
- Interacting with the DOM (e.g., animations, focus management)
- Synchronizing state with local storage or external systems
Best Practices
- Keep effects focused on side effects only.
- Avoid embedding business logic inside effects—this should remain in signals or computed signals.
- Prevent circular dependencies (e.g., an effect updating a signal that triggers the same effect again).
Signals vs Observables
| Feature | Signals | Observables |
| Nature |
Synchronous reactive value holder |
Asynchronous data stream |
| Data Flow |
Pull-based (value is read directly) |
Push-based (values are emitted to subscribers) |
| Primary Use Case |
Local UI state management |
Async operations (HTTP, events, streams) |
| Change Detection |
Fine-grained updates (only dependent parts re-render) |
Works with async pipe or manual subscription |
| Value Access |
Always holds a current value |
May or may not have a current value |
| Complexity |
Simple and lightweight API |
Powerful but involves operators and subscriptions |
| Subscription Required |
No |
Yes |
| Best For |
Component state and derived state |
Server calls, real-time data, event handling |
Advanced Concepts
1. Reactive Contexts
Signals automatically track dependencies through reactive contexts. When a Signal is accessed inside a computed() or effect(), Angular internally records that relationship. This means Angular knows exactly which computations or template bindings depend on which Signals. When a Signal changes, only the affected parts of the UI are updated — not the entire component. This fine-grained tracking leads to more efficient rendering and better performance compared to traditional change detection.
2. Equality Functions
By default, a Signal triggers updates whenever its value changes by reference. However, you can provide a custom equality function to control when updates should occur. This is useful when working with objects or arrays where structural comparison is preferred over reference comparison. A proper equality function can prevent unnecessary re-renders and improve performance in complex state scenarios
3. Integration with RxJS
Signals can interoperate with Observables using helper functions like toSignal() and toObservable()
Convert Observable -> Signal
Angular provides a convenient way to convert an Observable into a Signal, allowing you to integrate asynchronous streams with the new fine-grained reactivity model. Angular offers the toSignal() utility (from @angular/core/rxjs-interop) to convert an Observable into a Signal.
Example:
import { toSignal } from '@angular/core/rxjs-interop';
data$ = this.http.get<User[]>('/api/users');
dataSignal = toSignal(data$, { initialValue: [] });
When converting an Observable to a Signal, you must provide an initial value. Signals always need a current state, but Observables emit asynchronously. Supplying an initial value ensures the signal has a defined state right from the start, even before the first emission.
Convert Signal -> Observable
Angular provides the toObservable() utility (from @angular/core/rxjs-interop) to convert a Signal into an Observable. It creates an Observable that emits a new value whenever the Signal changes.
Example:
import { toObservable } from '@angular/core/rxjs-interop';
count$ = toObservable(this.countSignal);
This feature is especially handy when you need to apply RxJS operators like map, filter, or switchMap. It enables smooth integration of Signals into existing RxJS-based workflows, services, and third-party libraries.
Benefits of Signals
- Performance: Minimizes unnecessary re-rendering by updating only the parts of the UI that depend on changed values.
- Clarity: Clearly defines reactive dependencies, making data flow easier to understand and maintain.
- Ease of Use: Eliminates the need for manual subscriptions and complex reactive patterns.
- Future-Ready: Brings Angular in line with modern, fine-grained reactivity approaches.
Conclusion
An important development in Angular's reactivity architecture is Signals. Compared to previous patterns, they provide a more streamlined, effective, and predictable method of handling state. Signals are now the go-to option for handling local state and fine-grained, accurate user interface updates, even though Observables are still essential for overseeing asynchronous processes.