Skip to content

fix(forms/signals): surface debounce window as pending in validateAsync#68367

Open
surajy93 wants to merge 1 commit intoangular:mainfrom
surajy93:fix/validated-async-debounce-pending-state
Open

fix(forms/signals): surface debounce window as pending in validateAsync#68367
surajy93 wants to merge 1 commit intoangular:mainfrom
surajy93:fix/validated-async-debounce-pending-state

Conversation

@surajy93
Copy link
Copy Markdown
Contributor

@surajy93 surajy93 commented Apr 24, 2026

Previously, the pending state on a form field using validateAsync with debounce option was only set once the debounce duration elapsed and the HTTP call started. During the debounce window the field incorrectly reported pending as false, and stale validation errors from the previous value lingered until the debounce fired.

Fix by wrapping the resource returned from the factory with a computed status that returns 'loading' while the debounced params signal is still catching up (debouncedParams.isLoading()), falling back to the inner resource status otherwise.

This means:

  • form.field().pending() === true immediately on the first keystroke
  • Stale errors from the previous value disappear as soon as the user changes the input, not after the debounce duration

Fixes #68105

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #68105

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

@pullapprove pullapprove Bot requested a review from atscott April 24, 2026 14:40
@surajy93 surajy93 force-pushed the fix/validated-async-debounce-pending-state branch from f272b51 to 42447d5 Compare April 24, 2026 15:06
@ngbot ngbot Bot added this to the Backlog milestone Apr 24, 2026
Previously, the pending state on a form field using validateAsync with
debounce option was only set once the debounce duration elapsed and the
HTTP call started. During the debounce window the field incorrectly
reported pending as false, and stale validation errors from the previous
value lingered until the debounce fired.

Fix by wrapping the resource returned from the factory with a computed
status that returns 'loading' while the debounced params signal is still
catching up (debouncedParams.isLoading()), falling back to the inner
resource status otherwise.

This means:
- form.field().pending() === true immediately on the first keystroke
- Stale errors from the previous value disappear as soon as the user
  changes the input, not after the debounce duration

Fixes angular#68105

fix(forms): report pending status during debounce window in validateAsync

Previously, validateAsync only reported a pending status when the
underlying resource loader was active. This caused a UI delay where
the field would appear valid (or with old errors) during the debounce
window.

This change updates validateAsync to check the status of the internal
debounced resource and return 'pending' if it is currently in a
loading state (waiting for the debounce timer).

Added tests to verify pending status during basic debounce and
multiple sequential updates.
@surajy93 surajy93 force-pushed the fix/validated-async-debounce-pending-state branch from 42447d5 to dc513b6 Compare April 24, 2026 15:19
@surajy93
Copy link
Copy Markdown
Contributor Author

surajy93 commented Apr 25, 2026

Root Cause:

when user type source() changes so LinkedSignal() re-runs computation(). previous() is not undefined ( it was send from the first render), so it immediately return the same old snapshot object { status:'resolved', value: res.old}. No new object is created, no signal version bumps. nothing downstream invalidates.

resource.ts
image

debounce.ts -> the effect
so after one CD cycle after typing then only state changes

if (currentState.status !== 'loading' && currentState.status !== 'error') { state.set({status: 'loading', value: currentState.value}); }

image

Based on this proposed the solution because making changes in resource.ts will break the general flow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Signal forms: Include debounce duration in pending status

1 participant