Aller au contenu principal

linkedSignal (Angular 21)

What is linkedSignal?

linkedSignal est une nouvelle primitive d'Angular 21 qui crée un signal dérivé d'un autre signal, mais vous permet de remplacer sa valeur. C'est parfait pour les formulaires où vous souhaitez dériver l'état initial d'une source mais permettre les modifications utilisateur.

Why Use It?

  • Form fields - Initialize from data, allow edits
  • Derived but editable - Compute initial value, let user change it
  • Reset capability - Easily reset au derived value
  • Cleaner than computed + signal - No manual synchronization

linkedSignal vs computed

Featurecomputed()linkedSignal()
Read-only✅ Yes❌ No (writable)
Derived from source✅ Yes✅ Yes
Can be set manually❌ No✅ Yes
Cas d'utilisationValeurs dérivées puresValeurs dérivées éditables

Utilisation de base

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

@Component({
selector: 'app-user-form',
standalone: true,
template: `
<div>
<h3>Source: {{ sourceSignal() }}</h3>
<h3>Linked: {{ linkedValue() }}</h3>

<button (click)="changeSource()">Change Source</button>
<button (click)="changeLinked()">Change Linked</button>
<button (click)="reset()">Reset Linked</button>
</div>
`
})
export class UserFormComponent {
// Source signal
sourceSignal = signal('Initial');

// Linked signal - derives from source but can be changed independently
linkedValue = linkedSignal(() => this.sourceSignal());

changeSource() {
this.sourceSignal.set('Source Changed');
// linkedValue se met à jour automatiquement !
}

changeLinked() {
this.linkedValue.set('Manually Changed');
// Source stays the same
}

reset() {
// Réinitialiser à la valeur source actuelle
this.linkedValue.set(this.sourceSignal());
}
}

Key Points:

  • linkedSignal starts with value from sourceSignal
  • Lorsque sourceSignal change, linkedValue se met à jour
  • You can manually set linkedValue independently
  • Great for forms that initialize from data

Exemple de champ de formulaire

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

interface User {
id: number;
name: string;
email: string;
}

@Component({
selector: 'app-user-edit',
standalone: true,
template: `
<div>
<h2>Edit User</h2>

<label>
Name:
<input [(ngModel)]="editableName" />
</label>

<label>
Email:
<input [(ngModel)]="editableEmail" />
</label>

<div>
<button (click)="save()">Save</button>
<button (click)="reset()">Reset</button>
<button (click)="loadAnotherUser()">Load Another User</button>
</div>

<div>
<p>Original: {{ currentUser().name }} ({{ currentUser().email }})</p>
<p>Editing: {{ editableName() }} ({{ editableEmail() }})</p>
<p>Changed: {{ hasChanges() ? 'Yes' : 'No' }}</p>
</div>
</div>
`
})
export class UserEditComponent {
// Source data (e.g., from API)
currentUser = signal<User>({
id: 1,
name: 'John Doe',
email: 'john@example.com'
});

// Champs éditables liés à la source
editableName = linkedSignal(() => this.currentUser().name);
editableEmail = linkedSignal(() => this.currentUser().email);

// Vérifier si le formulaire a des changements
hasChanges = computed(() =>
this.editableName() !== this.currentUser().name ||
this.editableEmail() !== this.currentUser().email
);

save() {
// Sauvegarder les changements dans la source
this.currentUser.set({
...this.currentUser(),
name: this.editableName(),
email: this.editableEmail()
});
console.log('Saved!', this.currentUser());
}

reset() {
// Réinitialiser le formulaire aux données utilisateur actuelles
this.editableName.set(this.currentUser().name);
this.editableEmail.set(this.currentUser().email);
}

loadAnotherUser() {
// Lorsque la source change, les signaux liés se mettent à jour automatiquement !
this.currentUser.set({
id: 2,
name: 'Jane Smith',
email: 'jane@example.com'
});
// editableName et editableEmail se mettent à jour automatiquement !
}
}

Ce qui se passe :

  1. Les champs du formulaire s'initialisent depuis currentUser
  2. L'utilisateur peut modifier les champs
  3. Lorsque currentUser change (par ex., chargement d'un autre utilisateur), les champs du formulaire se mettent à jour automatiquement
  4. Peut facilement se réinitialiser aux valeurs originales

With Computation

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

@Component({
selector: 'app-price-editor',
standalone: true,
template: `
<div>
<label>
Base Price: ${{ basePrice() }}
<input type="number" [(ngModel)]="basePrice" />
</label>

<label>
Tax Rate: {{ taxRate() }}%
<input type="number" [(ngModel)]="taxRate" />
</label>

<label>
Final Price (editable):
<input type="number" [(ngModel)]="editableFinalPrice" />
</label>

<button (click)="resetPrice()">Reset to Calculated</button>

<p>Calculated: ${{ calculatedPrice() }}</p>
<p>Current: ${{ editableFinalPrice() }}</p>
</div>
`
})
export class PriceEditorComponent {
basePrice = signal(100);
taxRate = signal(10);

// Computed price
calculatedPrice = computed(() => {
const base = this.basePrice();
const tax = this.taxRate();
return base + (base * tax / 100);
});

// Editable price that starts with calculated value
editableFinalPrice = linkedSignal(() => this.calculatedPrice());
// L'utilisateur peut remplacer le prix, mais il se réinitialise lorsque les entrées changent

resetPrice() {
this.editableFinalPrice.set(this.calculatedPrice());
}
}

Cas d'utilisation : Calculateur de prix où vous pouvez remplacer le prix final, mais il utilise par défaut la valeur calculée.

Real-World Pattern: Search with Override

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

@Component({
selector: 'app-user-search',
standalone: true,
template: `
<div>
<input
[(ngModel)]="searchTerm"
placeholder="Search users..."
(input)="onSearch()" />

<div *ngFor="let user of users()">
{{ user.name }}
<button (click)="selectUser(user)">Select</button>
</div>

<div *ngIf="selectedUser()">
<h3>Selected User</h3>
<label>
Name: <input [(ngModel)]="editableName" />
</label>
<button (click)="resetName()">Reset to Original</button>
</div>
</div>
`
})
export class UserSearchComponent {
http = inject(HttpClient);

searchTerm = signal('');
users = signal<User[]>([]);
selectedUser = signal<User | null>(null);

// Nom lié à l'utilisateur sélectionné
editableName = linkedSignal(() => this.selectedUser()?.name ?? '');
// Se met à jour automatiquement lorsqu'un utilisateur différent est sélectionné !

onSearch() {
this.http.get<User[]>(`/api/users?q=${this.searchTerm()}`)
.subscribe(users => this.users.set(users));
}

selectUser(user: User) {
this.selectedUser.set(user);
// editableName se met à jour automatiquement au nom du nouvel utilisateur !
}

resetName() {
this.editableName.set(this.selectedUser()?.name ?? '');
}
}

Pourquoi linkedSignal est parfait ici :

  • Lorsque l'utilisateur sélectionne une personne différente, le champ du formulaire se met à jour automatiquement
  • L'utilisateur peut toujours modifier le nom
  • Facile à réinitialiser à la valeur originale

Bonnes Pratiques

  • ✅ Utiliser pour les champs de formulaire dérivés de sources de données
  • ✅ Parfait pour les formulaires d'édition qui chargent depuis une API
  • ✅ Idéal pour les paramètres qui peuvent être remplacés
  • ✅ Utiliser lorsque vous avez besoin d'une fonctionnalité de réinitialisation à la valeur par défaut
  • ✅ Combiner avec computed() pour les valeurs initiales dérivées
  • ❌ Ne pas utiliser si la valeur est purement calculée (utiliser computed())
  • ❌ Ne pas utiliser pour un état dérivé en lecture seule
  • ❌ Éviter s'il n'y a pas de signal source auquel se lier

linkedSignal vs Alternatives

ApprocheCas d'utilisation
linkedSignal()Derived from source, but can be edited
computed()Pure derived value, read-only
signal()Independent value, not derived
effect()Effets secondaires, pas pour dériver des valeurs

Common Use Cases

  • 📝 Edit forms - Load user data, allow edits, reset capability
  • ⚙️ Settings panels - Default values with overrides
  • 💰 Calculateurs de prix - Prix calculés qui peuvent être ajustés manuellement
  • 🔍 Search & select - Select item, edit copy, switch selections
  • 📊 Dashboard filters - Default filters derived from URL, can be changed
  • 🎨 Éditeurs de thèmes - Dérivés des préférences utilisateur, personnalisables