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:
- Pre-render hooks (
afterNextRenderregistered for "next"). - Update pass walks LViews.
- Post-render hooks (
afterRender). - 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 aninitialValue, arequireSyncmode, and an explicitInjector.toObservable(signal)— observe a signal as an Observable. Subscriptions are scoped to the activeInjector.
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()toinput().signal-output-migration— converts@Output()tooutput().signal-queries-migration— converts@ViewChild/@ContentChild/@ViewChildren/@ContentChildrento the signal equivalents.inject-migration— converts constructor injection toinject()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.
Related packages
@angular/forms/signals— the new signal-based forms API. Builds entirely on the primitives. See@angular/forms.@angular/router—withComponentInputBinding()is signal-aware: routed components can declareinput()properties for route params.
Where to start when modifying
- A new primitive: extend
packages/core/primitives/signals/src/and add tests undertest/. Maintain bundle-size budgets — these primitives ship with every app. - A new authoring API: add it under
packages/core/src/authoring/, extendngtscto recognize the call, add codegen, write a migration if it replaces a decorator-based API. - An RxJS interop change: edit
packages/core/rxjs-interop/and run the public API check.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.