Skip to main content

linkedSignal (Angular 21)

What is linkedSignal?

linkedSignal is a new Angular 21 primitive that creates a signal derived from another signal, but allows you to override its value. It's perfect for forms where you want to derive initial state from a source but allow user edits.

Why Use It?

  • Form fields - Initialize from data, allow edits
  • Derived but editable - Compute initial value, let user change it
  • Reset capability - Easily reset to 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
Use casePure derived valuesEditable derived values

Basic Usage

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 automatically updates!
}

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

reset() {
// Reset to current source value
this.linkedValue.set(this.sourceSignal());
}
}

Key Points:

  • linkedSignal starts with value from sourceSignal
  • When sourceSignal changes, linkedValue updates
  • You can manually set linkedValue independently
  • Great for forms that initialize from data

Form Field Example

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'
});

// Editable fields linked to source
editableName = linkedSignal(() => this.currentUser().name);
editableEmail = linkedSignal(() => this.currentUser().email);

// Check if form has changes
hasChanges = computed(() =>
this.editableName() !== this.currentUser().name ||
this.editableEmail() !== this.currentUser().email
);

save() {
// Save changes back to source
this.currentUser.set({
...this.currentUser(),
name: this.editableName(),
email: this.editableEmail()
});
console.log('Saved!', this.currentUser());
}

reset() {
// Reset form to current user data
this.editableName.set(this.currentUser().name);
this.editableEmail.set(this.currentUser().email);
}

loadAnotherUser() {
// When source changes, linked signals auto-update!
this.currentUser.set({
id: 2,
name: 'Jane Smith',
email: 'jane@example.com'
});
// editableName and editableEmail automatically update!
}
}

What happens:

  1. Form fields initialize from currentUser
  2. User can edit the fields
  3. When currentUser changes (e.g., loading another user), form fields automatically update
  4. Can easily reset to original values

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());
// User can override the price, but it resets when inputs change

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

Use case: Price calculator where you can override the final price, but it defaults to calculated value.

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);

// Name linked to selected user
editableName = linkedSignal(() => this.selectedUser()?.name ?? '');
// Auto-updates when different user is selected!

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

selectUser(user: User) {
this.selectedUser.set(user);
// editableName automatically updates to new user's name!
}

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

Why linkedSignal is perfect here:

  • When user selects different person, form field auto-updates
  • User can still edit the name
  • Easy to reset to original value

Best Practices

  • ✅ Use for form fields that derive from data sources
  • ✅ Perfect for edit forms that load from API
  • ✅ Great for settings that can be overridden
  • ✅ Use when you need reset to default functionality
  • ✅ Combine with computed() for derived initial values
  • ❌ Don't use if value is purely computed (use computed())
  • ❌ Don't use for read-only derived state
  • ❌ Avoid if no source signal to link to

linkedSignal vs Alternatives

ApproachUse Case
linkedSignal()Derived from source, but can be edited
computed()Pure derived value, read-only
signal()Independent value, not derived
effect()Side effects, not for deriving values

Common Use Cases

  • 📝 Edit forms - Load user data, allow edits, reset capability
  • ⚙️ Settings panels - Default values with overrides
  • 💰 Price calculators - Computed prices that can be manually adjusted
  • 🔍 Search & select - Select item, edit copy, switch selections
  • 📊 Dashboard filters - Default filters derived from URL, can be changed
  • 🎨 Theme editors - Derived from user preferences, customizable