Skip to content
Angular ng signals 4 min read

linkedSignal

Sometimes you need a piece of state that the user can edit freely, but that should also snap back to a sensible default whenever some upstream value changes. A plain writable signal can’t react to other signals, and a computed() is read-only — so historically you reached for an effect() to bridge the two, which is awkward and easy to get wrong. linkedSignal() (stable since Angular 19) solves exactly this: it produces a writable signal whose value is derived from a source, yet automatically resets when that source changes.

Why linkedSignal exists

Consider a product page with a list of available shipping options. The user picks one, but if the product changes, the previously selected option may no longer be valid and you want to fall back to the first available choice.

With a normal writable signal you’d have to manually watch the source and overwrite the selection inside an effect — which mixes derivation logic with side-effect plumbing and risks glitches or infinite loops. linkedSignal expresses the intent declaratively: “this value is computed from the source, but I can also set it directly until the source changes again.”

PrimitiveReadableWritableReacts to sources
signal()
computed()
linkedSignal()✅ (resets)

Basic usage

The simplest form takes a computation function. The returned signal recomputes — and discards any value you set — whenever a signal read inside the computation changes.

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

@Component({
  selector: 'app-shipping',
  standalone: true,
  template: `
    <select [value]="selected()" (change)="onChange($event)">
      @for (option of options(); track option) {
        <option [value]="option">{{ option }}</option>
      }
    </select>
    <p>Chosen: {{ selected() }}</p>
  `,
})
export class ShippingComponent {
  readonly options = signal(['Standard', 'Express', 'Overnight']);

  // Writable, but resets to options()[0] whenever options() changes.
  readonly selected = linkedSignal(() => this.options()[0]);

  onChange(event: Event) {
    this.selected.set((event.target as HTMLSelectElement).value);
  }

  swapToDigital() {
    // Changing the source resets `selected` back to the new first option.
    this.options.set(['Email', 'Download']);
  }
}

After the user picks Express, calling swapToDigital() replaces the option list. Because options() changed, selected recomputes to 'Email' rather than keeping the now-invalid 'Express'.

Output:

selected() -> "Standard"   // initial
selected() -> "Express"    // after user selects
selected() -> "Email"      // after swapToDigital(); reset to new options[0]

The source / computation form

The shorthand above resets on any dependency change. The expanded object form gives you precise control: you declare an explicit source, and the computation receives both the new source value and the previous state, letting you preserve the user’s choice when it’s still valid.

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

interface Product {
  id: number;
  variants: string[];
}

const product = signal<Product>({ id: 1, variants: ['S', 'M', 'L'] });

const chosenVariant = linkedSignal<Product, string>({
  source: product,
  computation: (newProduct, previous) => {
    // Keep the previous selection if it still exists in the new product.
    if (previous && newProduct.variants.includes(previous.value)) {
      return previous.value;
    }
    return newProduct.variants[0];
  },
});

Here previous is an object of shape { source, value } — the prior source value and the prior signal value — or undefined on first run. This makes “reset only when the choice becomes invalid” trivial to express.

Tip: Use the shorthand linkedSignal(() => ...) when any reset is acceptable, and the { source, computation } form when you need to reconcile the old value against the new source. Only signals read inside source (or inside the bare computation) trigger a reset.

How it differs from computed and effect

  • A computed() you cannot write to at all — there is no .set() or .update(). linkedSignal is a full WritableSignal.
  • An effect() runs side effects and shouldn’t set signals as its primary purpose; using it to mirror state is discouraged and can produce write-after-read warnings. linkedSignal is the supported, glitch-free replacement for that pattern.
  • Manual writes via set / update persist only until the next source change. They are intentionally ephemeral.
const count = signal(0);
const doubled = linkedSignal(() => count() * 2);

doubled.set(99);       // allowed
console.log(doubled()); // 99  — manual override holds
count.set(5);          // source changed -> recompute
console.log(doubled()); // 10  — override discarded

Output:

99
10

Best practices

  • Reach for linkedSignal whenever you find yourself writing an effect() solely to copy or reset one signal from another — it’s the idiomatic, leak-free alternative.
  • Prefer the explicit { source, computation } form when you want to preserve a valid prior selection; use the bare callback when any source change should fully reset.
  • Keep the computation pure — no HTTP calls, no DOM access, no logging. Side effects belong in effect(), asynchronous data belongs in resource().
  • Remember that manual writes are temporary by design; if you need permanent user state, store it in a plain signal() instead.
  • Type the generics (linkedSignal<S, V>) when source and value types differ, so previous.value is correctly inferred.
  • Avoid reading unrelated signals inside the computation — each one becomes a reset trigger you may not have intended.
Last updated June 14, 2026
Was this helpful?