linkedSignal() e un semnal scriibil a cărui valoare se recalculează automat când se schimbă sursa lui. Gândește-te la el ca la un computed() pe care îl poți și seta — până la următoarea schimbare a sursei, când „se resetează”. O folosire reală: un număr de pagină care revine la 1 când se schimbă filtrele, la Liste.
| signal() | computed() | linkedSignal() | |
|---|---|---|---|
| Se poate seta | ✓ | ✕ | ✓ |
| Se recalculează din alte semnale | ✕ | ✓ | ✓ |
| Exemplu tipic | filtre, toggle | total, listă filtrată | selecție, pagina curentă, draft |
Problema pe care o rezolvă
Ai o listă de opțiuni care depinde de altceva (țara aleasă) și o selecție pe care utilizatorul o poate schimba. Când lista se schimbă, selecția veche poate să nu mai fie validă. Înainte de linkedSignal, soluțiile erau proaste:
// Problema: selecția depinde de listă, dar utilizatorul o poate și schimba.
readonly options = computed(() => SHIPPING[this.country()]);
// ✗ computed nu se poate seta (utilizatorul nu poate alege altă opțiune)
readonly selected = computed(() => this.options()[0]);
// ✗ signal + effect: merge, dar e fragil (o randare cu valoare greșită, încă o rulare)
readonly selected = signal<ShippingOption | null>(null);
constructor() {
effect(() => this.selected.set(this.options()[0]));
}Forma scurtă și forma lungă
import { linkedSignal } from '@angular/core';
readonly options = computed(() => SHIPPING[this.country()]);
// ✓ Se recalculează când `options` se schimbă, DAR îl poți și seta manual.
readonly selected = linkedSignal(() => this.options()[0]);
choose(option: ShippingOption) {
this.selected.set(option); // valabil până la următoarea schimbare a lui options
}previous — păstrează selecția dacă există și în noua țară Pattern-ul „copie locală editabilă a unui input”
input() e read-only în componentă. Când vrei ca utilizatorul să editeze local o valoare primită de la părinte (un formular de editare, un draft), dar să se resincronizeze când părintele trimite altă valoare, linkedSignal e exact unealta potrivită. Înlocuiește vechiul combo ngOnChanges + câmp local.
@Component({
selector: 'app-name-editor',
template: `
<input [value]="draft()" (input)="draft.set($any($event.target).value)" />
@if (dirty()) { <span>modificat</span> }
<button (click)="save.emit(draft())">Salvează</button>
`,
})
export class NameEditor {
readonly name = input.required<string>(); // read-only, vine de la părinte
readonly save = output<string>();
// Copie locală, editabilă, care se RESETEAZĂ când părintele trimite alt nume.
protected readonly draft = linkedSignal(() => this.name());
protected readonly dirty = computed(() => this.draft() !== this.name());
}Opțiunea set — interceptarea scrierilor v22
Din v22.1 poți da o funcție set care primește valoarea dorită și un rawSet. Util pentru a normaliza sau limita valori (clamp, trim, rotunjire) într-un singur loc, indiferent de unde vine scrierea.
// Nou în v22.1: opțiunea `set` interceptează orice .set() / .update().
readonly stock = input.required<number>();
readonly quantity = linkedSignal(() => Math.min(1, this.stock()), {
set: (value, rawSet) => {
const clamped = Math.min(Math.max(Math.round(value) || 1, 1), this.stock());
rawSet(clamped); // scrierea „reală”
},
});
this.quantity.set(99); // → valoarea devine stock()
this.quantity.set(-3); // → 1set limitează la 1…5Alt exemplu clasic: paginare
// Exemplu clasic: pagina revine la 1 când se schimbă filtrele
readonly filters = signal<Filters>({ query: '', category: null });
readonly page = linkedSignal({
source: this.filters,
computation: () => 1,
});
nextPage() {
this.page.update((p) => p + 1); // utilizatorul navighează…
}
// …iar la orice schimbare de filtre page() redevine 1, automat.Când DA, când NU
- Selecție care depinde de o listă (dropdown dependent, tab activ, rând selectat).
- Pagina curentă care revine la 1 la schimbarea filtrelor.
- Copie editabilă a unui
input()(draft, formular de editare). - Valoare „implicită până când utilizatorul alege altceva”.
- Valoare pur derivată, pe care nimeni nu o setează →
computed(). - Stare independentă →
signal(). - Date de la server →
resource()(care are și el.set()pentru update optimist). - Two-way binding cu părintele →
model().
Capcane frecvente
- „Pierderea” modificărilor: orice schimbare a sursei suprascrie ce a setat utilizatorul. Dacă nu vrei asta, folosește forma lungă cu
previous. - Sursă prea largă: dacă sursa e un obiect mare care se schimbă des, selecția se resetează des. Fă sursa cât mai precisă (un
computeddoar cu ce contează) sau dă-iequal. - Tipuri în forma lungă: dacă TypeScript nu deduce bine, scrie explicit
linkedSignal<Sursă, Valoare>(...).
Fișă rapidă
// scurt: derivat + scriibil
const sel = linkedSignal(() => list()[0]);
// lung: sursă separată + valoarea anterioară
const sel2 = linkedSignal<Item[], Item>({
source: list,
computation: (items, prev) => items.find((i) => i.id === prev?.value.id) ?? items[0],
equal: (a, b) => a.id === b.id,
});
// v22.1: interceptare scriere
const qty = linkedSignal(() => 1, { set: (v, rawSet) => rawSet(Math.max(1, v)) });
sel.set(x); sel.update((v) => ...); sel(); // exact ca un signal()