Signals

This section documents modern, standalone Angular — signals, the built-in @if / @for / @switch control flow, @defer, typed reactive forms, provideHttpClient, functional guards and interceptors, and server-side rendering with hydration — as described by the official documentation at angular.dev, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against angular.dev before being relied on in production. Angular ships a major release roughly every six months and its APIs continue to evolve: the examples here target the current major release; where a consulted source disagrees with the current documentation, the documentation wins and the difference is noted.

This section’s bibliography lists the reference material consulted while preparing these pages.

A signal is a reactive value wrapper: reading it inside a reactive context (a template, a computed(), or an effect()) records a dependency, so any consumer re-runs automatically when the value changes. Signals are the default state primitive in modern Angular. See Signals.

signal()

signal(initialValue) returns a WritableSignal. Call it with no arguments to read; use .set(value) to replace it and .update(fn) to derive the next value from the current one.

import { signal } from '@angular/core';

const count = signal(0);

count();                      // read -> 0
count.set(10);                // replace
count.update((n) => n + 1);   // derive from current -> 11
<button (click)="count.set(count() + 1)">{{ count() }}</button>

By default a signal uses Object.is to decide whether the value changed; supply an equality function to treat structurally equal values as unchanged and skip notifying consumers.

import _ from 'lodash-es';

const point = signal({ x: 0, y: 0 }, { equal: _.isEqual });
point.set({ x: 0, y: 0 });   // deep-equal -> consumers do NOT re-run

computed()

computed(fn) creates a read-only signal derived from other signals. It is lazy (the body runs only when read), memoised (re-runs only when a tracked dependency changes), and glitch-free (never exposes a partially-updated intermediate value).

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

const price = signal(100);
const qty = signal(3);
const total = computed(() => price() * qty());   // tracks price and qty

total();          // 300
qty.set(4);
total();          // 400 -- recomputed on read

effect()

effect(fn) runs a side effect whenever any signal it reads changes. It runs once after creation, then again (asynchronously, batched, after change detection) on each dependency change. Register it in an injection context; it is cleaned up automatically on destroy. Return a cleanup callback via the onCleanup argument to tear down the previous run.

import { Component, effect, signal, inject } from '@angular/core';
import { Analytics } from './analytics';

@Component({ selector: 'app-search', templateUrl: './search.html' })
export class Search {
  readonly query = signal('');
  private readonly analytics = inject(Analytics);

  constructor() {
    effect((onCleanup) => {
      const q = this.query();
      const handle = setTimeout(() => this.analytics.track('search', q), 300);
      onCleanup(() => clearTimeout(handle));   // cancel if query changes again
    });
  }
}

Use untracked(fn) to read a signal without creating a dependency — so the effect reacts to some signals but merely samples others.

import { untracked } from '@angular/core';

effect(() => {
  const q = this.query();                       // reactive: re-runs when query changes
  const user = untracked(() => this.userId());   // sampled: changes here do NOT re-run
  this.log(q, user);
});

Prefer computed() for deriving state and reserve effect() for genuine side effects (logging, DOM APIs, syncing to localStorage); do not set() other signals from an effect. See Effects and the note on reading without tracking.

linkedSignal() and resource()

linkedSignal() produces a writable signal that also derives from a source: it recomputes when the source changes but can be overridden locally until the next source change — ideal for a "selected item" that resets when the list reloads.

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

const options = signal<string[]>(['sm', 'md', 'lg']);
const choice = linkedSignal(() => options()[0]);   // follows options, but writable

choice.set('lg');       // local override
options.set(['xs', 's']);
choice();               // 'xs' -- reset to the new default

resource() wraps an async data source in signals, exposing .value(), .status(), .error(), and .reload(), and cancels an in-flight request when its params change. rxResource() is the same backed by an Observable. HTTP-specific fetching (httpResource()) is covered on HTTP client.

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

const userId = signal(1);
const user = resource({
  params: () => ({ id: userId() }),
  loader: ({ params, abortSignal }) =>
    fetch(`/api/users/${params.id}`, { signal: abortSignal }).then((r) => r.json()),
});

user.status();   // 'idle' | 'loading' | 'resolved' | 'error' | 'reloading'

References: linkedSignal and resource.

Signals vs. RxJS

Both are reactive; they solve different problems.

Reach for a signal Reach for RxJS (see RxJS and async)

Synchronous UI state that always has a current value

Streams of events over time (keystrokes, websocket messages)

Derived values (computed)

Time-based combinators: debounceTime, throttleTime, bufferTime

Template bindings, component state

Cancellation and switching between requests (switchMap)

Inputs, model, and queries (all signal-based)

Complex multi-source orchestration and back-pressure

Bridge between them with toSignal(obs$) and toObservable(sig) from @angular/core/rxjs-interop. Signal inputs (input()), model (model()), and queries (viewChild() / contentChildren()) are all covered on Component communication. See RxJS interop.

A signal dependency chain

flowchart LR A["signal: price"] --> C["computed: subtotal"] B["signal: taxRate"] --> C C --> D["computed: total"] D --> E["effect: write total to localStorage"] D --> F["template binding"]