proposal-signals
proposal-signals copied to clipboard
Signal.subtle.watched/unwatched is not sufficient, we need to be notified when a signal is used (even without a watcher)
Hello, As a maintainer of the tansu signal library, I am trying (here) to re-implement it using signal-polyfill and I came across the following difference of behavior, that I would like to solve, avoiding any breaking change.
With tansu (inspired by svelte stores), as described further in this article we have the ability to know whether a signal is used or not, in order to update the signal value and add or remove event listeners. For example, we can create the following signal that contains a string value from the local storage and a computed that parses the value as JSON:
import {readable, computed} from '@amadeus-it-group/tansu';
// this signal is synchronized with the "preferences" item in local storage
const preferences$ = readable(null as string | null, (set) => {
// this function is called when the signal starts to be used
const updateFromStorage = (e: StorageEvent) => {
if (e.key === "preferences") {
set(e.newValue);
}
};
window.addEventListener('storage', updateFromStorage);
set(localStorage.getItem("preferences"));
return () => {
// this function is called when the signal is no longer used
window.removeEventListener('storage', updateFromStorage)
};
});
const parsedPreferences$ = computed(() => JSON.parse(preferences$() ?? "null"));
Now if I call parsedPreferences$()
at any time, I have the current value of the "preferences" local storage item parsed as a json object.
However, with the current signals proposal, this is not possible to achieve without constantly listening to the "storage" event.
I know the Signal.subtle.watched
and Signal.subtle.unwatched
callbacks, but they are not called when the value is read through a one-time access to the value of a dependent signal:
import { Signal } from 'signal-polyfill';
const updateFromStorage = (e: StorageEvent) => {
if (e.key === "preferences") {
preferences$.set(e.newValue);
}
}
const preferences$ = new Signal.State(null as string | null, {
[Signal.subtle.watched]: () => {
preferences$.set(localStorage.getItem("preferences"));
window.addEventListener('storage', updateFromStorage);
},
[Signal.subtle.unwatched]: () => {
window.removeEventListener('storage', updateFromStorage);
}
})
const parsedPreferences$ = new Signal.Computed(() => JSON.parse(preferences$.get() ?? "null"));
With the above code, if I read parsedPreferences$.get()
once (without having a watcher) and then the value of the "preferences" local storage item changes from another tab, and then I read again (still without having a watcher) parsedPreferences$.get()
, the value will not be up-to-date.
I believe it is fundamental that the signals proposal allows such a use case.