Open-Source Wikis

/

Angular

/

Features

/

Signals

angular/angular

Signals

The reactivity primitives that have become the framework's preferred state model. Implemented as a standalone primitive package at packages/core/primitives/signals/ and integrated into the runtime under packages/core/src/render3/reactivity/.

Mental model

A signal is a cell holding a value plus a list of dependents. Reading a signal inside a "reactive context" (a computed, effect, or template) registers the context as a consumer; writing to the signal notifies all consumers.

graph LR
  Producer["signal(initial)"] -->|read by| ComputedNode["computed(...)"]
  Producer -->|read by| EffectNode["effect(...)"]
  Producer -->|read by| LView["LView (template binding)"]
  ComputedNode -->|read by| AnotherEffect["another effect(...)"]

When signal.set(...) runs, every consumer is marked dirty. Effects re-run on the next microtask; computeds recompute lazily on next read; LViews schedule a tick.

Authoring API

Function Purpose
signal(initial) Creates a writable signal.
computed(() => ...) A signal whose value is derived from other signals. Recomputes lazily.
effect(() => ...) A side effect that runs whenever any signal it reads changes.
linkedSignal({...}) A writable signal that tracks an upstream signal, allowing local override. Used by signal forms.
untracked(() => ...) Reads inside the callback don't register as consumers.
toSignal(observable) Convert an RxJS Observable to a signal. From @angular/core/rxjs-interop.
toObservable(signal) Convert a signal to an Observable. Same package.

Component authoring APIs

These wrap signals as the new component-input/output/query primitives:

API Replaces File
input<T>(default?, options) @Input() decorator packages/core/src/authoring/input/
output<T>() @Output() decorator packages/core/src/authoring/output/
model<T>(default?, options) Two-way binding via [(prop)] packages/core/src/authoring/model/
viewChild<T>(selector) @ViewChild decorator packages/core/src/authoring/queries/
viewChildren<T>(selector) @ViewChildren decorator same
contentChild<T>(selector) @ContentChild decorator same
contentChildren<T>(selector) @ContentChildren decorator same

These compile to the same Ivy directive metadata, but expose the values through signals so templates and effects can react to them naturally.

Integration with the Ivy runtime

The runtime registers each LView as a ReactiveLViewConsumer (packages/core/src/render3/reactive_lview_consumer.ts). When a signal read happens during the LView's update pass, the consumer is added to the signal's dependent list. When the signal changes, only LViews that read it get marked dirty.

This is what makes zoneless mode efficient: tick walks are precisely scoped to the views the change affects, rather than top-down dirty-marking.

Compiler integration

ngtsc recognizes calls to input(), output(), model(), and the query factories during analysis and synthesizes the matching directive metadata (signalInputs, signalOutputs, etc.) so the runtime knows to wire them into LView slots correctly. The IR for signal inputs differs slightly from decorator inputs because the runtime layout is different.

Effects vs lifecycle hooks

Effects are scheduled by the same mechanism as tick() runs but execute during the tick, not in a separate pass. The execution order:

  1. Pre-render hooks (afterNextRender registered for "next").
  2. Update pass walks LViews.
  3. Post-render hooks (afterRender).
  4. Effects flush.

If your effect mutates state another component reads, schedule a re-tick by setting another signal. The framework handles re-runs without infinite loops as long as the dependency graph is acyclic.

RxJS interop

@angular/core/rxjs-interop provides:

  • toSignal(obs, options) — subscribe to an Observable, expose its latest value as a signal. Supports an initialValue, a requireSync mode, and an explicit Injector.
  • toObservable(signal) — observe a signal as an Observable. Subscriptions are scoped to the active Injector.

The two together let new code use signals as the primary reactivity primitive while continuing to consume RxJS-based APIs (HTTP, router events, custom observables).

Migrations

packages/core/schematics/migrations/ ships:

  • signal-input-migration — converts @Input() to input().
  • signal-output-migration — converts @Output() to output().
  • signal-queries-migration — converts @ViewChild/@ContentChild/@ViewChildren/@ContentChildren to the signal equivalents.
  • inject-migration — converts constructor injection to inject() calls (a prerequisite for many signal patterns).

Each migration ships with the framework. Apps run them via ng update @angular/core --migrate-only=signal-input-migration.

  • @angular/forms/signals — the new signal-based forms API. Builds entirely on the primitives. See @angular/forms.
  • @angular/routerwithComponentInputBinding() is signal-aware: routed components can declare input() properties for route params.

Where to start when modifying

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Signals – Angular wiki | Factory