Watch signal#69800
Conversation
|
That would address #62236 btw ! |
8f1114d to
b4340e4
Compare
| interface InternalCoreGlobalUtils { | ||
| toggleWatchSignal(id: string): void; | ||
| } |
There was a problem hiding this comment.
Consider: I'm wondering the abstraction between DevTools and core should include the string signal ID or directly keep a reference to the raw signal value? I know eventually we need to convert it into a serializable ID, but should that be done at the core or DevTools layers?
For example, an alternative approach might look like:
// core/...
type Dispose = () => void;
interface InternalCoreGlobalUtils {
watchSignal(node: ReactiveNode): Dispose;
}
function watchSignal(node: ReactiveNode): Dispose {
const watch = createWatch(...);
return () => void watch.destroy();
}Then DevTools can manage the IDs and maps:
// devtools/...
const watchedSignals = new Map<string, WeakRef<{dispose: Dispose}>>();
function toggleWatchSignal(id: string): void {
const watched = watchedSignals.get(id);
if (watched) {
watched.dispose();
watchedSignals.delete(id);
} else {
const stopWatching = ng.watchSignal(getSignalById(id));
watchedSignals.set(id, new WeakRef({dispose: stopWatching}));
}
}The potential benefits from my perspective are:
- Cleaner abstraction between core and DevTools.
- Less complexity in core, given that DevTools is the only consumer which should care about IDs in this context.
- Potentially works if the user directly types in the browser console
ng.watchSignal(someSignalFn), given they wouldn't be able to easily access signal IDs.
OTOH, I'm not convinced putting ReactiveNode in the ng global type is a great idea in the first place, given that we're trying to create a stronger abstraction not tied to a particular framework. However, signals are already converged across Angular and Wiz, and if you don't have signals (like ACX), then you could just omit this function entirely.
I'm not sure this is necessarily better than what you have, but just something to think about.
There was a problem hiding this comment.
I think what I struggled with when originally prototyping the design you're suggesting was getting the signal getter function from the ReactiveNode to ensure the Watch or effect being created that logs the new value has a dependency on the signal and would run whenever it was updated. It appears I don't need to get the signal getter function since we can just call producerAccessed on the ReactiveNode. Do you have a preference? Happy to refactor if you feel strongly.
There was a problem hiding this comment.
I don't feel very strongly, up to you if this idea is 1. meaningfully better and 2. worth the effort to implement compared to what you have here.
…ging Implement \`toggleWatchSignal(id)\` in \`signal_debug.ts\` to enable toggling reactive watch listeners on individual signal graph nodes. - Create reactive \`Watch\` instances using \`createWatch\` to log signal value/state changes to the console when active. - Use \`WeakRef\` mapping and \`FinalizationRegistry\` (\`watchCleanupRegistry\`) so active watches do not retain strong references to signal nodes or prevent garbage collection. - Expose \`watched\` status on \`DebugSignalGraphNode\` and publish \`toggleWatchSignal\` onto \`window.ng\` in development mode. - Add acceptance tests covering watch activation, signal mutation logging, manual disposal, GC cleanup, and safe invalid ID handling.
Add a "Watch signal" button to the signal-details component to allow users to "watch" changes to the associated signal.
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
There is no way to "watch" a signal (log the new value programmatically on change) in Angular.
Issue Number: #62236
What is the new behavior?
There is a button in the signal graph details pane in devtools that allows the user to "watch" or "unwatch" a signal.
Does this PR introduce a breaking change?
Other information