All Articles
AngularTypeScriptRxJS

Angular Signals: Reactive State Without RxJS

How Angular Signals simplify reactivity for most use cases and when you still want to reach for RxJS Observables.

2026-04-10·8 min read

Angular Signals landed as a developer preview in Angular 16 and became stable in Angular 17. They represent the most significant shift in Angular's reactivity model since the introduction of RxJS Observables, and they solve a very specific problem: most component state does not need the full power of an Observable stream.

The Problem Signals Solve

In a typical Angular component, you might write:

@Component({
  template: `<p>{{ count }}</p>`,
})
export class CounterComponent {
  count = 0;

  increment() {
    this.count++;
  }
}

This works, but Angular uses Zone.js change detection to know when to re-render — which means running change detection on the entire component tree on every microtask. Signals let Angular track exactly which template expressions depend on which values, enabling fine-grained, zone-free change detection.

Creating and Reading Signals

import { signal, computed, effect } from '@angular/core';

const count = signal(0);

// Read the value
console.log(count()); // 0

// Write
count.set(1);
count.update(v => v + 1);

Signals are functions — you call them to read their current value. This is intentional: it makes reads explicit and traceable by the Angular runtime.

Computed Signals

computed() creates a derived signal that re-evaluates lazily when its dependencies change:

const doubled = computed(() => count() * 2);
console.log(doubled()); // 2 after count is 1

Angular caches the computed value and only recalculates when one of the signals it read has changed. There is no distinctUntilChanged to add — this is built in.

Effects

effect() runs a side effect whenever its dependencies change:

effect(() => {
  console.log(`Count is now: ${count()}`);
});

Effects are useful for logging, syncing state to localStorage, or triggering non-Angular side effects. They should not be used to update other signals — use computed() for derived state instead.

Signals vs Observables

Signals and Observables are complementary, not competing. The rule of thumb:

| Use Signals for... | Use Observables for... | |---|---| | Component state | HTTP requests | | Derived/computed values | Event streams | | Template bindings | Debounced input | | Simple two-way binding | Complex async pipelines |

Angular provides toSignal() and toObservable() to bridge the two worlds when needed.

Signal-Based Inputs (Angular 17.1+)

Angular 17.1 introduced signal-based inputs, replacing @Input() decorators:

@Component({ ... })
export class CardComponent {
  title = input.required<string>();
  subtitle = input('');
}

Signal inputs are read-only signals — you cannot .set() them from inside the component. They work naturally with computed():

displayTitle = computed(() => this.title().toUpperCase());

Conclusion

Signals are the future of state management in Angular. They are simpler to reason about than Observables for local component state, they enable better performance through fine-grained change detection, and they reduce the boilerplate around Zone.js. Start adopting them for new components today — the migration path from existing code is gradual and well-supported.