LinkedSignal in Angular 19: Say Goodbye to Effect-Based State Sync
Angular 19 introduces a powerful new reactive primitive: linkedSignal. This feature represents a significant shift in how we handle dependent state management, reducing our reliance on Effects.
What is LinkedSignal?
A linkedSignal represents state that is reset based on a provided computation. Conceptually, it maintains state that is only valid within the context of another source signal. This makes it perfect for scenarios where one piece of state depends on another.
Key Features
1. Two Forms of Usage
Shorthand Form
const counter = signal(0);
const doubledCounter = linkedSignal(() => counter() * 2);
Full Form with Source Tracking
const items = signal(['A', 'B', 'C']);
const selectedItem = linkedSignal({
source: items,
computation: (currentItems, previous) => {
// Access to both current and previous state
return previous && currentItems.includes(previous.value)
? previous.value
: currentItems[0];
}
});
2. Writable Signal Capabilities
LinkedSignals implement the WritableSignal interface, meaning they support:
Reading values with
()
Setting values with
.set()
Updating values with
.update()
Converting to readonly with
.asReadonly()
3. Previous Value Access
One of the most powerful features is access to the previous state in computations:
linkedSignal({
source: parentState,
computation: (current, previous) => {
if (!previous) return defaultValue;
return determineNewValue(current, previous.value);
}
});
Common Use Cases
Currently, when developers need to synchronize two pieces of state, they often reach for effects. Consider a common scenario: you have a list of items and a selected item. When the list changes, you want to reset or update the selection. Many developers implement this using effects, which isn't ideal for state management.
Let me show you a practical implementation using a cats facts application, where we'll demonstrate the power of linkedSignal.
1. Selection State Management
// Before (using Effect)
@Component({...})
class CatFactsComponent {
factsResource = this._catFactsService.getCatsFacts;
selectedItem = signal<string | null>(null);
constructor() {
effect(() => {
this.factsResource.value(); // Watch for changes
this.selectedItem.set(null); // Reset selection
});
}
}
// After (using LinkedSignal)
@Component({...})
class CatFactsComponent {
factsResource = this._catFactsService.getCatsFacts;
selectedItem = linkedSignal<string[] | undefined, number | null>({
source: this.factsResource.value,
computation: (items) => null
});
}
2. Smart Selection with Previous State
In the previous example if I reload my request, the selection will become null and not item will be selected. But playing with the previous value we can still have the previous selected index when we refreshing data:
selectedItem = linkedSignal<string[] | undefined, number>({
source: this.factsResource.value,
computation: (facts, previous) => {
if (previous && facts && previous.value < facts.length) {
return previous.value; // Preserve valid selections
}
return -1; // Reset when needed
}
});
3. Pagination
You current page selection will be a linkedSignal. It will become the first one when we have no data and we will keep the current page when refreshing a we have more data than our items per page.
currentPage = linkedSignal<string[] | undefined, number>({
source: this.factsResource.value,
computation: (facts, previous) => {
if (!facts) return 0;
if (previous && previous.value * this.itemsPerPage() < facts.length) {
return previous.value; // Keep current page if still valid
}
return 0; // Reset to first page if data changes significantly
}
});
You can find the full implementation in the source code appended at the end of this post.
Advantages Over Previous Approaches
No race conditions like we might see with effects
Clearer intention in the code
Better TypeScript support
More predictable behavior
Computed vs LinkedSignal
Writability
computed
: Read-only, can't modify directlylinkedSignal
: Writable, can.set()
and.update()
State Management
computed
: Pure derivation, always reflects sourcelinkedSignal
: Maintains its own state, recomputes on source changes
Previous Value Access
computed
: No access to previous valuelinkedSignal
: Access to previous source and value
Use Cases
// ✨ Computed: For derived data
const totalPrice = computed(() =>
items().reduce((sum, item) => sum + item.price, 0)
);
// ✨ LinkedSignal: For dependent state
const selectedItem = linkedSignal({
source: items,
computation: (items, prev) =>
prev?.value && items.includes(prev.value)
? prev.value
: null
});
Conclusion
While linkedSignal looks promising for handling dependent state, it's important to note:
⚠️ WARNING: This is still experimental!
🙏 Call to Action: The Angular team needs our help! Try it out and provide feedback:
Test it in different scenarios
Share edge cases you find
Suggest improvements
Report bugs
Propose new use cases
Remember: Your feedback shapes the future of Angular! Let's help make linkedSignal even better before it's finalized.
🔗 Ready to experiment? Check the PR.
🔗 Link to the repository of the examples provided in this post.
📢 Share your thoughts.
Thanks for reading so far 🙏
I’d like to have your feedback so please leave a comment, clap or follow. 👏
Spread the Angular love! 💜
If you really liked it, share it among your community, tech bros and whoever you want! 🚀👥
Don't forget to follow me and stay updated: 📱
Thanks for being part of this Angular journey! 👋😁