Is this a regression?
The previous version in which this bug was not present was
21
Description
Bug:
debounce() from signal forms breaks input when used on a matAutocomplete field (CVA write-back uses the debounced model value) in angular 22
When a signal-forms field uses debounce() and is bound via [formField] to an <input matInput [matAutocomplete]>, typing into the input is broken: characters get reverted/erased while the debounce window is
pending, and the model value is never updated correctly.
The same [formField] + debounce() combination works fine on a plain <input matInput> / <textarea matInput> (no autocomplete). The problem only appears when MatAutocompleteTrigger is on the element.
Reproduction
minimal repro: (stackblitz still has no support for node 22.22.3 required by angular 22)
import { Component, signal } from '@angular/core';
import { debounce, form, FormField } from '@angular/forms/signals';
import { bootstrapApplication } from '@angular/platform-browser';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
@Component({
selector: 'app-root',
template: `
<mat-form-field>
<input
type="text"
matInput
[formField]="autocompleteForm.input"
[matAutocomplete]="auto"
/>
<mat-autocomplete #auto="matAutocomplete">
@for (option of options; track option) {
<mat-option [value]="option">{{ option }}</mat-option>
}
</mat-autocomplete>
</mat-form-field>
`,
imports: [FormField, MatFormFieldModule, MatInputModule, MatAutocompleteModule],
})
export class LoginApp {
autocompleteModel = signal<{ input: string }>({ input: '' });
autocompleteForm = form(this.autocompleteModel, (f) => {
debounce(f.input, 1_000);
});
options: string[] = ['One', 'Two', 'Three'];
}
bootstrapApplication(LoginApp);
Steps: start typing in the field.
Expected behavior
Typed characters stay in the input. The debounce only delays the propagation of the UI value to the form model (field().value()); it must not interfere with what the user sees/types. (This is exactly how it
behaves for a plain matInput without autocomplete.)
Actual behavior
While the debounce window is pending, the typed text is reverted/cleared, because the stale (pre-debounce) model value is written back into the input on every change detection cycle.
Root cause analysis (AI generated)🤖
The issue is the interaction between the ControlValueAccessor write-back path in @angular/forms/signals and MatAutocompleteTrigger.writeValue.
Signal forms keep two signals per field:
- controlValue → immediate UI value
- value → model value, updated only after the debounce resolves (debounceSync() → sync()).
The directive write-back path differs depending on how the control is bound:
- Native / custom-control path (plain matInput, textarea) writes back the immediate value:
const controlValue = state.controlValue(); // immediate
if (bindingUpdated(...)) setNativeControlValue(input, controlValue);
- → stays in sync with typing; debounce only delays value(). ✅
- CVA path (cvaControlCreate, taken because MatAutocompleteTrigger provides NG_VALUE_ACCESSOR) writes back the debounced model value:
const value = fieldState.value(); // DEBOUNCED
if (bindingUpdated(...)) untracked(() => controlValueAccessor.writeValue(value));
So on each keystroke:
1. _handleInput → _onChange(typed) → controlValue.set(typed) → debounce starts; value() still holds the old value.
2. The input event triggers change detection → the directive's update reads the stale value() and calls writeValue(staleValue).
3. MatAutocompleteTrigger.writeValue → _assignOptionValue → _updateNativeInputValue sets this._formField._control.value = staleValue, overwriting what the user just typed.
Effectively, the CVA write-back keeps stomping the input with the stale debounced model value while the debounce is pending.
Suggested direction
The signal-forms CVA write-back path should write back controlValue() (the immediate value), consistently with the native/custom-control paths, instead of the debounced value(). Otherwise debounce() is
fundamentally incompatible with any ControlValueAccessor-based control (not just matAutocomplete).
This may need to be triaged on the @angular/forms side rather than @angular/components, since the defective write-back lives in cvaControlCreate in @angular/forms/signals; MatAutocompleteTrigger just happens
to be a CVA that surfaces it.
Is this a regression?
The previous version in which this bug was not present was
21
Description
Bug:
debounce()from signal forms breaks input when used on a matAutocomplete field (CVA write-back uses the debounced model value) in angular 22When a signal-forms field uses debounce() and is bound via
[formField]to an<input matInput [matAutocomplete]>, typing into the input is broken: characters get reverted/erased while the debounce window ispending, and the model value is never updated correctly.
The same
[formField] + debounce()combination works fine on a plain<input matInput> / <textarea matInput>(no autocomplete). The problem only appears when MatAutocompleteTrigger is on the element.Reproduction
minimal repro: (stackblitz still has no support for node 22.22.3 required by angular 22)
Steps: start typing in the field.
Expected behavior
Typed characters stay in the input. The debounce only delays the propagation of the UI value to the form model (field().value()); it must not interfere with what the user sees/types. (This is exactly how it
behaves for a plain matInput without autocomplete.)
Actual behavior
While the debounce window is pending, the typed text is reverted/cleared, because the stale (pre-debounce) model value is written back into the input on every change detection cycle.
Root cause analysis (AI generated)🤖