signal関数は、Angularコードで状態を保持するために使用できます。この状態は、他の状態に依存することがあります。例えば、ユーザーが注文の配送方法を選択できるコンポーネントを考えてみましょう。
@Component({
/* ... */
})
export class ShippingMethodPicker {
shippingOptions: Signal<ShippingMethod[]> = getShippingOptions();
// デフォルトで最初の配送オプションを選択します。
selectedOption = signal(this.shippingOptions()[0]);
changeShipping(newOptionIndex: number) {
this.selectedOption.set(this.shippingOptions()[newOptionIndex]);
}
}
この例では、selectedOptionは最初のオプションにデフォルト設定されますが、ユーザーが別のオプションを選択すると変更されます。しかし、shippingOptionsはシグナルであり、その値は変更される可能性があります!shippingOptionsが変更されると、selectedOptionはもはや有効なオプションではない値を含む可能性があります。
**linkedSignal関数は、本質的に他の状態に_リンク_された状態を保持するシグナルを作成できます。**上記の例を再考すると、linkedSignalはsignalを置き換えることができます。
@Component({
/* ... */
})
export class ShippingMethodPicker {
shippingOptions: Signal<ShippingMethod[]> = getShippingOptions();
// selectedOptionを最初の配送オプションに初期化します。
selectedOption = linkedSignal(() => this.shippingOptions()[0]);
changeShipping(index: number) {
this.selectedOption.set(this.shippingOptions()[index]);
}
}
linkedSignalはsignalと同様に動作しますが、重要な違いが1つあります。デフォルト値を渡す代わりに、computedのように算出関数を渡します。算出値が変更されると、linkedSignalの値は計算結果に変更されます。これは、linkedSignalが常に有効な値を持つようにするのに役立ちます。
次の例は、linkedSignalの値がリンクされた状態に基づいてどのように変化するかを示しています。
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'
以前の状態を考慮する
場合によっては、linkedSignalの計算でlinkedSignalの以前の値を考慮する必要があります。
上記の例では、shippingOptionsが変更されると、selectedOptionは常に最初のオプションに戻って更新されます。しかし、選択したオプションがリスト内にまだ存在する場合は、ユーザーの選択を維持したい場合があります。これを実現するには、別々のソースと算出を使用してlinkedSignalを作成できます。
interface ShippingMethod {
id: number;
name: string;
}
@Component({
/* ... */
})
export class ShippingMethodPicker {
constructor() {
this.changeShipping(2);
this.changeShippingOptions();
console.log(this.selectedOption()); // {"id":2,"name":"Postal Service"}
}
shippingOptions = signal<ShippingMethod[]>([
{id: 0, name: 'Ground'},
{id: 1, name: 'Air'},
{id: 2, name: 'Sea'},
]);
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(index: number) {
this.selectedOption.set(this.shippingOptions()[index]);
}
changeShippingOptions() {
this.shippingOptions.set([
{id: 0, name: 'Email'},
{id: 1, name: 'Sea'},
{id: 2, name: 'Postal Service'},
]);
}
}
linkedSignalを作成する際には、算出関数だけを提供する代わりに、個別のsourceプロパティとcomputationプロパティを持つオブジェクトを渡すことができます。
sourceは、computedやコンポーネントのinputなどの任意のシグナルにできます。linkedSignalは、sourceが変更されたとき、またはcomputation内で参照されている任意のシグナルが変更されたときに、提供されたcomputationの結果でその値を更新します。
computationは、sourceの新しい値とpreviousオブジェクトを受け取る関数です。previousオブジェクトには、previous.source(sourceの以前の値)、previous.value(linkedSignalの以前の値)の2つのプロパティがあります。これらの以前の値を使用して、計算の新しい結果を決定できます。
HELPFUL: previousパラメーターを使用する場合、linkedSignalのジェネリック型引数を明示的に指定する必要があります。最初のジェネリック型はsourceの型に対応し、2番目のジェネリック型はcomputationの出力型を決定します。
カスタムの等価比較
linkedSignalは他のシグナルと同様に、カスタムの等価比較関数を設定できます。この関数は、下流の依存関係によってlinkedSignalの値(計算結果)が変更されたかどうかを判断するために使用されます。
const activeUser = signal({id: 123, name: 'Morgan', isAdmin: true});
const activeUserEditCopy = linkedSignal(() => activeUser(), {
// `id`が同じであれば、ユーザーは同じとみなします。
equal: (a, b) => a.id === b.id,
});
// または、`source`と`computation`を分ける場合
const activeUserEditCopy = linkedSignal({
source: activeUser,
computation: (user) => user,
equal: (a, b) => a.id === b.id,
});
Customizing the set operation
Sometimes you may want the set and update operations of a linkedSignal to write back to the source of truth instead of updating the linkedSignal's value directly. You can customize this behavior by passing a set function in the options.
The custom set function receives two arguments:
- The new value being set.
- A
rawSetfunction, which you can invoke to update thelinkedSignal's internal state directly (matching the default behavior).
NOTE: Using rawSet allows you to update the linkedSignal's value directly. This can be useful to prevent the computation from running, for example if it is an expensive derivation and you already know the result.
Writing back to a source signal
Consider a component that displays and allows editing temperature in Fahrenheit, but uses a Celsius signal as its source of truth:
const tempC = signal(0);
const tempF = linkedSignal(() => (tempC() * 9) / 5 + 32, {
set: (valF) => tempC.set(((valF - 32) * 5) / 9),
});
console.log(tempF()); // 32
// Setting Fahrenheit updates Celsius, which reactively updates Fahrenheit
tempF.set(212);
console.log(tempC()); // 100
console.log(tempF()); // 212
Updating a property inside a parent object
Another common scenario is updating a specific property inside a parent object. The parent is held in a signal, and you link to a nested property:
interface Order {
id: number;
shippingMethod: string;
}
const order = signal<Order>({
id: 42,
shippingMethod: 'Ground',
});
const shippingMethod = linkedSignal(() => order().shippingMethod, {
set: (newMethod) => {
// Perform an immutable update to write the change back to the order
order.update((currentOrder) => ({
...currentOrder,
shippingMethod: newMethod,
}));
},
});
console.log(shippingMethod()); // 'Ground'
// Updating the shippingMethod updates the parent order object
shippingMethod.set('Air');
console.log(order()); // { id: 42, shippingMethod: 'Air' }
console.log(shippingMethod()); // 'Air'