IMPORTANT: linkedSignal
is developer preview. It's ready for you to try, but it might change before it is stable.
You can use the signal
function to hold some state in your Angular code. Sometimes, this state depends on some other state. For example, imagine a component that lets the user select a shipping method for an order:
@Component({/* ... */})export class ShippingMethodPicker { shippingOptions: Signal<ShippingMethod[]> = getShippingOptions(); // Select the first shipping option by default. selectedOption = signal(this.shippingOptions()[0]); changeShipping(newOptionIndex: number) { this.selectedOption.set(this.shippingOptions()[newOptionIndex]); }}
In this example, the selectedOption
defaults to the first option, but changes if the user selects another option. But shippingOptions
is a signal— its value may change! If shippingOptions
changes, selectedOption
may contain a value that is no longer a valid option.
The linkedSignal
function lets you create a signal to hold some state that is intrinsically linked to some other state. Revisiting the example above, linkedSignal
can replace signal
:
@Component({/* ... */})export class ShippingMethodPicker { shippingOptions: Signal<ShippingMethod[]> = getShippingOptions(); // Initialize selectedOption to the first shipping option. selectedOption = linkedSignal(() => this.shippingOptions()[0]); changeShipping(index: number) { this.selectedOption.set(this.shippingOptions()[index]); }}
linkedSignal
works similarly to signal
with one key difference— instead of passing a default value, you pass a computation function, just like computed
. When the value of the computation changes, the value of the linkedSignal
changes to the computation result. This helps ensure that the linkedSignal
always has a valid value.
The following example shows how the value of a linkedSignal
can change based on its linked state:
const shippingOptions = signal(['Ground', 'Air', 'Sea']);const selectedOption = linkedSignal(() => shippingOptions()[0]);console.log(selectedOption()); // 'Ground'selectedOption.set(shippingOptions()[2]);console.log(selectedOption()); // 'Sea'shippingOptions.set(['Email', 'Will Call', 'Postal service']);console.log(selectedOption()); // 'Email'
Accounting for previous state
In some cases, the computation for a linkedSignal
needs to account for the previous value of the linkedSignal
.
In the example above, selectedOption
always updates back to the first option when shippingOptions
changes. You may, however, want to preserve the user's selection if their selected option is still somewhere in the list. To accomplish this, you can create a linkedSignal
with a separate source and computation:
@Component({/* ... */})export class ShippingMethodPicker { shippingOptions: Signal<ShippingMethod[]> = getShippingOptions(); selectedOption = linkedSignal<ShippingMethod[], ShippingMethod>({ // `selectedOption` is set to the `computation` result whenever this `source` changes. source: this.shippingOptions, computation: (newOptions, previous) => { // If the newOptions contain the previously selected option, preserve that selection. // Otherwise, default to the first option. return newOptions.find(opt => opt.id === previous?.value?.id) ?? newOptions[0]; } }); changeShipping(newOptionIndex: number) { this.selectedOption.set(this.shippingOptions()[newOptionIndex]); }}
When you create a linkedSignal
, you can pass an object with separate source
and computation
properties instead of providing just a computation.
The source
can be any signal, such as a computed
or component input
. When the value of source
changes, linkedSignal
updates its value to the result of the provided computation
.
The computation
is a function that receives the new value of source
and a previous
object. The previous
object has two properties— previous.source
is the previous value of source
, and previous.value
is the previous result of the computation
. You can use these previous values to decide the new result of the computation.
Custom equality comparison
linkedSignal
updates to the result of the computation every time its linked state changes. By default, Angular uses referential equality to determine if the linked state has changed. You can alternatively provide a custom equality function.
const activeUser = signal({id: 123, name: 'Morgan'});const email = linkedSignal(() => `${activeUser().name}@example.com`, { // Consider the user as the same if it's the same `id`. equal: (a, b) => a.id === b.id,});// Or, if separating `source` and `computation`const alternateEmail = linkedSignal({ source: activeUser, computation: user => `${user.name}@example.com`, equal: (a, b) => a.id === b.id,});// This update to `activeUser` does not cause `email` or `alternateEmail`// to update because the `id` is the same.activeUser.set({id: 123, name: 'Morgan', isAdmin: false});