effect, linkedSignal and untracked

Angular · Practical · 13 min

Real apps must sync signals with storage, logging and third-party code, and keep dependent state consistent.

`effect(fn)` runs fn once and again whenever a signal it read changes. Effects are for side effects that leave the signal world: saving to localStorage, logging, analytics, imperatively updating a canvas or third-party widget.

effect((onCleanup) => {
  const id = setTimeout(() => save(this.draft()), 500);
  onCleanup(() => clearTimeout(id));
});
  • Create effects in an injection context (constructor or field initializer); they are destroyed with their component or service.
  • Effects run asynchronously as part of change detection, not synchronously on every set.
  • Avoid using effects to copy state between signals. Use computed for derived values and linkedSignal for resettable state.
  • For DOM work after rendering, use afterRenderEffect.

`linkedSignal` is a writable signal whose value is reset whenever a source changes. A classic case: the selected option in a list should default to the first option whenever the list changes, but the user can still select another one:

selected = linkedSignal(() => this.options()[0]);

A long form linkedSignal({ source, computation: (src, previous) => ... }) lets you keep the previous selection if it still exists.

`untracked(() => sig())` reads a signal without making it a dependency, useful inside effects and computeds when one value should not trigger re-runs.

Example

import { Component, signal, computed, effect, linkedSignal, untracked } from '@angular/core';
@Component({
  selector: 'app-shipping',
  template: `
    <select (change)="country.set($any($event.target).value)">
      <option>NL</option><option>US</option>
    </select>
    @for (m of methods(); track m) {
      <label><input type="radio" [checked]="m === method()" (change)="method.set(m)" /> {{ m }}</label>
    }`,
})
export class Shipping {
  protected country = signal('NL');
  protected methods = computed(() => (this.country() === 'US' ? ['UPS', 'FedEx'] : ['PostNL', 'DHL']));
  // resets to the first method when methods() changes, but stays user-writable
  protected method = linkedSignal(() => this.methods()[0]);
  private userId = signal(42);
  constructor() {
    effect(() => {
      const m = this.method();                 // dependency
      console.log('save', untracked(this.userId), m);   // userId is not a dependency
      localStorage.setItem('shipping', m);
    });
  }
}

Remember

  • effect is for side effects outside signals; create it in an injection context
  • linkedSignal = writable state that resets when its source changes
  • untracked reads a signal without creating a dependency

Watch out for

  • Using an effect to set one signal from another; that causes extra cycles and glitches. Use computed or linkedSignal.

Check yourself

  1. A list of options can change, and the selection should reset to the first option when it does, while users can still pick another. What fits best?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: angular.dev/guide/signals/linked-signal