Introduction

For years, Angular has depended on Observables and its Change Detection mechanism to handle reactivity. With the launch of Angular 16, a new reactive primitive called Signals was introduced, providing a more fine-grained, predictable, and efficient approach to managing state updates. Signals mark a significant evolution in Angular’s reactivity model, aiming to streamline state management while improving rendering performance and overall application efficiency

What Are Signals?

A Signal is a reactive container that holds a value and automatically notifies its dependents whenever that value changes. In contrast to Observables—which are push-based and typically asynchronous—Signals operate synchronously and provide more transparent state tracking.

Key Characteristics

Why Angular Introduced Signals

Before Signals, Angular’s reactivity model leaned on:

While these tools were powerful, they came with notable drawbacks:

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.

Types of Signals

1) Writable Signals

Writable signals expose an API that allows you to modify their values directly. They are created by invoking the signal function and passing in the initial value as an argument.

Example:

import { signal } from '@angular/core';

const counter = signal(0);
counter.set(1); // updates value
counter.update(value => value + 1); // increments
  

Core Methods

Method NameDescription
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

Why They’re Useful

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

Best Practices

Signals vs Observables

FeatureSignalsObservables
NatureSynchronous reactive value holderAsynchronous data stream
Data FlowPull-based (value is read directly)Push-based (values are emitted to subscribers)
Primary Use CaseLocal UI state managementAsync operations (HTTP, events, streams)
Change DetectionFine-grained updates (only dependent parts re-render)Works with async pipe or manual subscription
Value AccessAlways holds a current valueMay or may not have a current value
ComplexitySimple and lightweight APIPowerful but involves operators and subscriptions
Subscription RequiredNoYes
Best ForComponent state and derived stateServer 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

Conclusion

Signals mark a significant advancement in Angular’s reactivity model. They offer a cleaner, more efficient, and predictable approach to handling state compared to earlier patterns. Although Observables are still crucial for managing asynchronous operations, Signals have become the preferred choice for local state handling and precise, fine-grained UI updates.