Reputation: 61
Question to the Angular experts about Signals:
When you create a computed()
signal, dependencies are automatically tracked, but according to the docs only for signals that were previously read.
If I understand this correctly, would the following be problematic since the every
could potentially shortcircuit if the one of the first items is not selected, making the computed function only track a subset of the items?
const allSelected = computed(() => {
return selectableItems.every(item => item.selected());
});
To be clear: item.selected()
is a signal as well.
It seems to be running fine, but I want to be sure.
Upvotes: 4
Views: 2923
Reputation: 20494
Your particular issue of concern shouldn't be an issue. For selectableItems
to be true all items will have to be evaluated. If an item turns to false, it doesn't matter if an item with a greater index becomes false in the future because logically the result of every
will still be the same.
That being said, you will have issues if selectableItems
can have items added or removed.
An extreme example is if selectableItems
is empty when computed
is called. At that point, no signals will fire and the value will never be checked again.
A less extreme issue would be if an item is added at a later point. The value of allSelected
won't be evaluated until one of the previous items have their value changed. If all the old items are true and the new item is false, then allSelected
will be wrong.
If selectableItems
can change, then a better design might be to make selectableItems
a signal instead of all the individual items, and then use update when an item is changed.
// changed selectableItems to items to make example shorter.
items = signal([] as SelectableItem[]);
const allSelected = computed(() => this.items().every(item => item.selected));
addItem(item: SelectableItem) {
this.items.update(x => [...x, item]);
}
updateItem(item: SelectableItem) {
this.items.update(x => {
const index = x.indexOf(item);
return [...x.slice(0, index), item, ...x.slice(index + 1, 0)];
});
}
Advanced Solution
If you wanted to keep the logic of your array outside of your component you could even create your own signal. The solution below adds methods to a writable signal and uses undocumented but exported functions from core signal primitives.
export interface ArraySignal<T> extends WritableSignal<T[]> {
updateElement(item: T, updateFn: (x: T) => T): void;
push(item: T): void;
}
export function arraySignal<T>(initialValue: T[]): ArraySignal<T> {
const internal = signal(initialValue) as SignalGetter<T[]> & WritableSignal<T[]>;
const node = internal[SIGNAL];
return Object.assign(internal, {
push: (item: T) => signalMutateFn(node, (x) => {
x.push(item);
return x;
}),
updateElement: (item: T, updateFn: (x: T) => T) => {
signalMutateFn(node, (x) => {
const index = x.indexOf(item);
(index !== -1) && (x[index] = updateFn(item));
})
}
})
}
Upvotes: 5