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
signal(value)creates writable state; read by calling it (count()), write withset()orupdate().computed(() => …)derives values lazily and memoizes them; dependencies are tracked automatically.effect(() => …)runs side effects when signals change. Use sparingly, not for deriving state.- Component APIs:
input(),input.required(),output(),model(), plus signal-basedviewChild()andcontentChildren(). resource()/httpResource()load async data reactively;linkedSignal()gives writable derived state.toSignal()andtoObservable()bridge RxJS. Signals enable zoneless change detection.
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
linkedSignal()creates a writable signal that resets when a source changes, for example "the selected option defaults to the first item whenever the list changes, but the user can pick another".resource({ params, loader })runs an async loader whenever its reactive params change, exposingvalue(),isLoading(),error(),status(), andreload().httpResource()is theHttpClient-based variant, andrxResource()accepts Observables. They cancel stale requests via an abort signal, which avoids race conditions.
RxJS Interop
toSignal(observable$, { initialValue })subscribes and exposes the latest value as a signal (auto-unsubscribes with the component).toObservable(signal)turns a signal into an Observable for RxJS pipelines (debounce, switchMap, retries).- Use RxJS for complex event streams (websockets, typeahead with debounce and cancellation, combining streams over time) and signals for state that templates read.
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
- Angular — The framework overview
- Angular Components — Standalone components and templates
- Angular Forms — Reactive forms and signal forms
- State Management — Patterns across frameworks
- SolidJS — Another signals-based framework
- TypeScript — Typed signals and inputs