DEV Community

Giorgio Galassi
Giorgio Galassi

Posted on

Angular v19+ — computed vs linkedSignal signals 🔥🚀

Angular’s move toward a signal‑first ecosystem didn’t end with resource(), rxResource() or httpResource(). Those primitives made async flows cleaner, but they also exposed a deeper shift: the way we model state is changing. When you start structuring components around signals, two primitives quickly become the backbone of state organization: computed and linkedSignal.

At a glance they seem similar, but they solve very different problems. One focuses on pure derivation. The other acts as a controlled bridge between component state and something external. Understanding the difference is key to avoiding subtle bugs.


🔧 Signals in Angular — a quick recap

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

const count = signal(0);
const double = computed(() => count() * 2);
Enter fullscreen mode Exit fullscreen mode

A signal stores writable state. A computed value is automatically recalculated whenever its dependencies change. You never set it manually, and it shouldn’t perform side effects. It’s a clean, predictable way to model values that depend entirely on other signals.

linkedSignal builds on this mechanism but shifts the focus: instead of deriving values, it synchronizes state with a source that lives outside the local signal graph.


📐 What computed actually does

A computed value is nothing more than a function of other signals. Its job is to keep a derived value always in sync with its inputs. You give it the signals it depends on, and Angular takes care of the rest.

For example, if you have two form fields and want a combined value and a quick validity check, a computed is exactly the right tool:

const firstName = signal('');
const lastName = signal('');

const fullName = computed(() => `${firstName()} ${lastName()}`.trim());
const isValid = computed(() => fullName().length > 0);
Enter fullscreen mode Exit fullscreen mode

Here everything is deterministic: every value can be recalculated at any time from the source signals. You never push data back, and nothing lives outside the model. As long as your flow is one‑way, state that drives other state, computed is the cleanest and simplest option.

Where it falls short is when you need to mirror or update something that doesn’t originate inside the component. For that, it’s the wrong tool.


🔗 Why linkedSignal exists

Some state relationships require a value to reset reactively when its source changes while still remaining fully writable by the user. A computed can handle the reactive reset but cannot be written to. A plain signal can be written to, but it cannot reactively recalculate itself from a source. linkedSignal fills this exact gap.

Instead of wiring effects to manually reset values, a pattern that becomes noisy and risks unintended write loops, linkedSignal expresses the relationship declaratively: “derive from this source unless the user overrides it.”

Under the hood, linkedSignal listens to the source and recalculates its value whenever that source changes.

🔍 Note:

The computation function receives two arguments: the new source value and a previous object. previous.source holds the source value before the update, while previous.value contains the previous linkedSignal value, including user overrides. This allows you to implement simple resets, conditional resets, or more advanced transition rules based on both old and new state.

In parallel, the linked signal continues to expose the usual writable signal API (set, update), so user overrides remain straightforward.


🧪 Two practical examples

Pure derived state — computed

If a value can always be derived directly from its inputs and never needs manual intervention, a computed is the simplest fit. Filtering a list based on user input is a classic example where nothing external is involved and no override is necessary.

const items = signal<string[]>(['Angular', 'React', 'Vue']);
const filter = signal('a');

const filteredItems = computed(() =>
  items().filter(item => item.toLowerCase().includes(filter().toLowerCase()))
);
Enter fullscreen mode Exit fullscreen mode

Reactive default with user override — linkedSignal

A more nuanced scenario is when the value depends on another piece of state but must still allow direct user edits. Consider an app with a theme selector and a user‑adjustable contrast slider.

The contrast should reset to the optimal default whenever the theme changes, but the user must be able to override that value afterward.

// The source signal
selectedTheme = signal<'light' | 'dark'>('light');

// Linked signal that recomputes when the theme changes
contrastLevel = linkedSignal<'light' | 'dark', number>({
  source: selectedTheme,
  computation: (newTheme) => {
    // Default contrast for dark theme
    if (newTheme === 'dark') return 80; 
    return 50; // Default contrast for light theme
  }
});

// User interaction and manual override
onContrastChanged(newContrast: number) {
  this.contrastLevel.set(newContrast);
}
Enter fullscreen mode Exit fullscreen mode

In this setup, linkedSignal makes the relationship explicit and predictable. When the theme changes, contrastLevel recomputes based on the new theme. When the user adjusts the value, the override takes priority. This avoids side‑effect‑driven resets and keeps the logic clean and declarative.

🧠 How to reason about the two

The difference is straightforward once you think in terms of data direction.

A computed value is one‑way. Data flows from your signals into the computed output. There’s no feedback loop and no external synchronization.

A linkedSignal is two‑way. It sits between your component and another system. Reading it pulls in the current external value, while updating it pushes data out.

Whenever you need writable state that remains aligned with an external layer, linkedSignal is the right choice.


🎯 Picking the right tool

Use a computed when the value is entirely derived from other signals and doesn’t need to be written manually. It’s ideal for internal one‑direction flows within a component.

Use a linkedSignal when the component must expose a writable signal that represents an external state and must stay synchronized with it. This applies to inputs, stores, and any layer where the source of truth lives outside the component.

When you’re unsure, start with computed. If you later realize you’re wiring manual read‑and‑write glue between the component and external state, that’s usually the moment when switching to linkedSignal cleans everything up.


✨ Closing thoughts

computed and linkedSignal address distinct needs. The first keeps derived values predictable and local. The second gives you a structured way to mirror and update external state without abandoning the signal mindset.

Most of your application logic will rely on signal and computed. Reach for linkedSignal only when synchronization with something external is required.


If you found this helpful, follow me here and on LinkedIn for more deep dives into Angular, web performance, and modern frontend development.

See you in the next one! 🤙🏻
 — G.

Top comments (0)