Repository navigation
Conversation
A custom debouncer's abort listener runs late and inside the `pendingSync` linkedSignal computation. Signal writes in the listener throw NG0600, signal reads can drop the typed value, and a finished debounce is aborted on the next touch. These specs assert the expected behavior and fail on purpose. The triage notes explain the root cause and the measured fix options. A fix will replace the notes once maintainers pick an option.
The spec "should be ignored if value is directly set before it resolves" awaited `debounceResult.resolve` without calling it, so the debounce never resolved and the spec never reached the late resolve. Call `resolve()` and check that the late debounce doesn't write the model. The check fails today: the late `sync()` calls `value.set()` with the value that's already current. The triage notes explain how this relates to the open questions on the fix.
Member
|
Going to close the PRs as they're won't me merged as is. Let's move the discussions to the issues. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue #71631 | Triage notes | Repro specs
Why the change
A custom debouncer's abort listener runs late and inside a
linkedSignalcomputation, so a listener that touches a signal throws NG0600 or drops the user's value; this draft adds failing repro specs and asks you to choose the fix before I write it.Special things to note
the existing direct-write spec in
debounce.spec.ts, which now actually resolves its debounce.packages/forms/signals/triage/.debounce(path, ms)anddebounce(path, 'blur'), aren't affected.Change outline
What the branch adds:
Where the abort runs today, in the normal type, wait, blur flow:
Recommended fix: make
pendingSynca plain fieldAbort explicitly and untracked, and clear the field when a debounce completes
(
src/field/node.ts):Measured as a temporary patch: the full node target and the full Chromium suite pass, except the
spec for an immediate abort on a direct write (decision 1). It also removes one
linkedSignalfromevery
FieldNode, debounced or not.Alternative: keep the
linkedSignaland make it safecomputation: (_source, previous) => { - previous?.value?.abort(); + untracked(() => previous?.value?.abort()); return undefined; }, await promise; - if (controller.signal.aborted) { + if (controller.signal.aborted || this.structure.isOrphaned()) { return; // Do not sync if the debounce was aborted. } + // Reading `pendingSync` aborts `controller` if `value` changed while it waited. + if (untracked(this.pendingSync) !== controller) { + return; + } + // The debounce is done, so a later read of `pendingSync` must not abort it. + this.pendingSync.set(undefined);Measured with the same result. The diff is smaller, but user listeners still run during a
computation. The orphan check must come first: reading
pendingSyncreadsvalue, which throwsNG01904 on a removed array item.
Design decisions
When the abort fires. The guide and the
pendingSyncdoc comment say it fires when the valuechanges before the debounce resolves. Both fixes keep today's timing for direct model writes: the
abort fires at the next keystroke, touch, or reset.
effectper debounced field, and effects runasynchronously, so it still isn't immediate.
Current guide text (
async-operations.md:472):Proposed guide text:
Plain field or
linkedSignal. Recommended: plain field. No user code runs inside acomputation, and nothing aborts a finished debounce by construction.
A debounce that resolves after a direct model write. Today its
sync()writes the currentvalue back, which is a no-op for plain fields. On a compat field it calls
FormControl.setValue()and emitsvalueChangesagain. ThevalueBeforecheck skips thatsync(). One flow changes with the plain field: touching after a direct write now reachessync()influshSync(), where today the recompute returnsundefinedand skips it. The samecheck would keep that flow as it is today; the measured patch has it only in
debounceSync().The existing spec "should be ignored if value is directly set before it resolves" already states
this contract, but it awaited
resolvewithout calling it, so it never reached the late resolve.This PR fixes the spec and checks that the late debounce doesn't write the model:
Both measured fixes skip that
sync(), so they make it pass.Questions for maintainers
the debounce right away?
pendingSyncbecome a plain field?sync(), as the existingspec's name says? If so, should
flushSync()skip it too?them in
untracked().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?
Issue Number: #71631
What is the new behavior?
No behavior changes yet. This PR adds failing repro specs and triage notes. The fix will replace the
notes on this branch once the questions above are answered.
Does this PR introduce a breaking change?