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
computedfor derived values andlinkedSignalfor 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
effectis for side effects outside signals; create it in an injection contextlinkedSignal= writable state that resets when its source changesuntrackedreads 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.