Angular Signals are central to efficient application state management. Choosing between linked signal and computed signal is an important decision. This article explains the differences in detail.
Signals basics recap
First, a quick review. Signals are the foundation of Angular's new reactive programming model. Basic usage looks like this:
// Basic signal
const count = signal(0);
// computed signal
const doubledCount = computed(() => count() * 2);
// effect
effect(() => {
console.log(`Count changed: ${count()}`);
});
Signals offer a simple API for state management. Still, the difference between linked signal and computed signal can be surprisingly subtle.
Signal, computed signal, and linked signal
Angular Signals have three main types. Let us look at each.
Signal (basic signal)
A Signal is the basic building block of Angular reactive programming. It holds a single value, supports reads and updates, and lets other Signals or UI components watch changes.
// Basic signal usage example
const count = signal(0);
// Read the value
console.log(count()); // Output: 0
// Update the value
count.set(1);
console.log(count()); // Output: 1
// Update the value based on its current value
count.update(value => value + 1);
console.log(count()); // Output: 2
Use Signals as the basic unit for application state. They fit simple state management.
Computed Signal
A Computed Signal derives a value from other Signals. When dependencies change, it recomputes automatically. It is read-only—you cannot set it directly—and you cannot access the previous value.
// computed signal usage example
const firstName = signal('Taro');
const lastName = signal('Yamada');
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // Output: Taro Yamada
firstName.set('Ichiro');
console.log(fullName()); // Output: Ichiro Yamada
Computed Signals recompute only when a dependency changes, which matters for performance. They are especially useful in cases like:
// Internal behavior example
const a = signal(1);
const b = signal(2);
const c = computed(() => {
console.log('Recalculation ran');
return a() + b();
});
// Initial read
console.log(c()); // Output: 3, "Recalculation ran"
// When the value of a changes
a.set(2);
console.log(c()); // Output: 4, "Recalculation ran"
// When the value of b changes
b.set(3);
console.log(c()); // Output: 5, "Recalculation ran"
Linked Signal
Linked Signal is more flexible than Computed Signal. You can access the previous value and set the value directly. It also handles more complex state transitions and external events.
// linked signal usage example
const inputText = signal('');
const filteredText = linkedSignal(
inputText,
(current, previous) => {
// Access the current and previous values
if (current.length < previous.length) {
return current; // Keep it as-is when characters were removed
}
return current.toUpperCase(); // Capitalize it when characters were added
}
);
Internally, whenever the source Signal changes, the callback runs with both the current and previous values. The return value becomes the new linked value.
// Internal behavior example
const source = signal(0);
const linked = linkedSignal(
source,
(current, previous) => {
console.log(`Current value: ${current}, previous value: ${previous}`);
return current * 2;
}
);
// Change the value
source.set(1); // Output: "Current value: 1, previous value: 0"
console.log(linked()); // Output: 2
source.set(2); // Output: "Current value: 2, previous value: 1"
console.log(linked()); // Output: 4
Which should you use? Concrete criteria
Which one should you pick in real development? Here are concrete criteria:
When to use computed signal
Computed Signal fits simple calculations and transforms. Use it when you derive a value from other Signals and do not need the previous value. It also suits performance-sensitive cases.
Examples:
// Calculate the total amount
const items = signal([
{ name: 'Apple', price: 100 },
{ name: 'Banana', price: 200 }
]);
const total = computed(() =>
items().reduce((sum, item) => sum + item.price, 0)
);
This computes the total price of items in a list. When items are added or removed, the total updates automatically. Simple aggregation is a Computed Signal sweet spot.
// Filtering
const searchTerm = signal('');
const filteredItems = computed(() =>
items().filter(item =>
item.name.includes(searchTerm())
)
);
This filters items by search keyword. When the keyword changes, the filtered list updates. Deriving new values from other Signals is a typical Computed Signal use case.
When to use linked signal
Linked Signal fits when you want different behavior based on the previous value, or when external events should set the value directly. It also suits complex state transitions and async results.
Examples:
// Undo/Redo functionality
const history = linkedSignal(
currentState,
(current, previous) => {
if (current.action === 'undo') {
return previous; // Return to the previous state
}
return current; // Move to the new state
}
);
This implements undo/redo over user actions. Compare current and previous state: on undo, go back; on redo, advance. When you need previous state to branch logic, Linked Signal is the right tool.
// Manage asynchronous processing state
const apiState = linkedSignal(
fetchState,
async (current, previous) => {
if (current.status === 'loading') {
try {
const data = await fetchData();
return { status: 'success', data };
} catch (error) {
return { status: 'error', error };
}
}
return current;
}
);
This manages API call state. Handle loading, success, and error appropriately. Linked Signal fits async results and state changes driven by external events.
Practical example: form validation
A common project example is form validation. Linked Signal can manage form state efficiently.
const formData = signal({
email: '',
password: ''
});
// Validation state
const validationState = linkedSignal(
formData,
(current, previous) => {
const errors = {
email: current.email ? '' : 'Email is required',
password: current.password.length >= 8 ? '' : 'Password must be at least 8 characters'
};
// Compare with the previous state and update the error message
if (JSON.stringify(errors) !== JSON.stringify(previous?.errors)) {
return { ...current, errors };
}
return previous;
}
);
// Form submission state
const submitState = linkedSignal(
validationState,
async (current, previous) => {
if (current.isSubmitting) {
try {
await submitForm(current);
return { ...current, isSubmitting: false, isSubmitted: true };
} catch (error) {
return { ...current, isSubmitting: false, error };
}
}
return current;
}
);
Performance considerations
The choice affects performance. Computed Signals need memory to track dependencies but recompute only when dependencies change, which is efficient. Linked Signals need memory for the previous value and run the callback on every source change.
Angular's runtime optimizes Computed Signals automatically. For Linked Signals, you may need manual optimization in some cases.
Summary
Understanding Signal, Computed Signal, and Linked Signal—and using each appropriately—helps you manage Angular application state more efficiently.
Use basic Signals for simple state, Computed Signals for values derived from other Signals, and Linked Signals when you need previous values or more complex transitions.
Computed Signals favor performance; Linked Signals favor direct updates from external events.
Using Signals well leads to more maintainable Angular applications.
See you next time.