Angular Signals

Signals are Angular's reactive primitive: a value wrapper that notifies consumers when it changes. A template or computed value that reads a signal is tracked automatically, so when the signal updates, Angular knows exactly what to recompute and which views to refresh. That replaces much of the need for zone.js-based change detection and for RxJS in simple state scenarios, making apps faster and easier to reason about.

Since their introduction in Angular 16, signals have become central to modern Angular: signal inputs (input()), model() two-way bindings, resource() for async data, signal-based queries, and zoneless change detection all build on them. RxJS remains valuable for complex event streams, and the two interoperate cleanly.

TL;DR

Quick Example

Core Concepts

Writable Signals

Signals notify consumers when their value changes (compared with Object.is by default; configure equal for custom comparison). Update objects and arrays immutably (update(list => [...list, item])). Mutating in place doesn't change the reference, so consumers won't be notified.

Computed Signals

computed() derives a value from other signals. It's lazy (computed on first read) and memoized (recomputed only when dependencies change), and its dependencies are tracked dynamically: only signals actually read during the last computation count. Computed signals are read-only, which makes them ideal for derived state like filtered lists, totals, and validation flags.

Effects

effect() runs a function whenever the signals it reads change, which is appropriate for synchronizing with non-reactive APIs: localStorage, logging, analytics, imperative third-party widgets, and canvas drawing. Effects run asynchronously during change detection, and they're cleaned up with their injection context. Don't use effects to set other signals for derived state (use computed or linkedSignal); that leads to extra cycles and hard-to-follow flows.

Signal-Based Component APIs

Signal inputs combine naturally with computed: fullName = computed(() => \${this.first()} ${this.last()}\) updates whenever either input changes, with no ngOnChanges needed.

linkedSignal and resource

RxJS Interop

Zoneless Change Detection

Traditionally, zone.js patched async APIs to trigger change detection after every event and timer. With signals (plus markForCheck and events), Angular knows precisely when state changes, so apps can run zoneless (provideZonelessChangeDetection()), which means smaller bundles, fewer unnecessary checks, and simpler debugging. It's the default for new applications in recent versions. Pair it with ChangeDetectionStrategy.OnPush for existing components.

State Management With Signals

For many apps, services holding signals are enough:

Expose read-only signals, and mutate only through methods. For larger apps, NgRx SignalStore adds structure (state, computed, methods, entities, devtools). See state management.

Best Practices

Derive With computed, Not effect

If a value can be calculated from other signals, make it a computed. Reserve effect for talking to the outside world.

Keep Signal Updates Immutable

Always produce new arrays and objects in set and update. In-place mutation doesn't notify consumers, and it breaks memoization.

Adopt Signal Inputs and New Control Flow Together

input(), @if/@for, and OnPush or zoneless change detection combine into simpler, faster components. Angular provides migration schematics for inputs, outputs, and queries.

Use resource for Async Data Tied to State

Loading data that depends on inputs or route parameters is exactly what resource and httpResource handle: automatic refetching, loading and error states, and cancellation. That avoids hand-written subscriptions.

Common Mistakes

Mutating Signal Values

Using effect to Sync Derived State

Forgetting to Call the Signal in Templates

{{ count }} renders the function, not its value. Use {{ count() }}. Signals are read by calling them, in templates too.

FAQ

Do signals replace RxJS in Angular?

Not entirely. Signals are simpler and better for synchronous state that templates read. RxJS remains excellent for streams of events over time (debouncing, websockets, complex async coordination). Angular provides toSignal and toObservable so you can use each where it fits.

What's the difference between computed and effect?

computed produces a new, memoized, read-only value derived from other signals, and it has no side effects. effect performs side effects (writing to storage, calling APIs, manipulating DOM outside Angular) when signals change. Deriving state belongs in computed.

What is zoneless Angular?

An application mode without zone.js, where change detection runs only when Angular knows state changed (signal updates, template events, markForCheck, async pipe emissions). It reduces bundle size and unnecessary change detection cycles, and it's the recommended direction for modern Angular apps.

How do signal inputs differ from @Input?

Signal inputs (input()) are read-only signals, so you can derive values with computed and react with effect instead of ngOnChanges. They support required inputs and transforms, and they integrate with fine-grained change detection. They're the recommended API for new components.

Related Topics

References