diff --git a/packages/forms/signals/src/directive/control_cva.ts b/packages/forms/signals/src/directive/control_cva.ts
index 38f95eb4632..5857d68452f 100644
--- a/packages/forms/signals/src/directive/control_cva.ts
+++ b/packages/forms/signals/src/directive/control_cva.ts
@@ -88,12 +88,12 @@ export function cvaControlCreate(
return () => {
const fieldState = parent.state();
- const value = fieldState.value();
+ const controlValue = fieldState.controlValue();
- if (bindingUpdated(bindings, 'controlValue', value)) {
+ if (bindingUpdated(bindings, 'controlValue', controlValue)) {
// We don't know if the interop control has underlying signals, so we must use `untracked` to
// prevent writing to a signal in a reactive context.
- untracked(() => parent.controlValueAccessor!.writeValue(value));
+ untracked(() => parent.controlValueAccessor!.writeValue(controlValue));
}
for (const name of CONTROL_BINDING_NAMES) {
diff --git a/packages/forms/signals/test/web/interop.spec.ts b/packages/forms/signals/test/web/interop.spec.ts
index 45328911030..c2789874040 100644
--- a/packages/forms/signals/test/web/interop.spec.ts
+++ b/packages/forms/signals/test/web/interop.spec.ts
@@ -607,6 +607,62 @@ describe('ControlValueAccessor', () => {
expect(field().value()).toBe('initial');
});
+ it('should not write stale model values back to a CVA while debounce is pending', () => {
+ let writeValues: string[] = [];
+
+ @Component({
+ selector: 'custom-control-writeback-test',
+ template: ``,
+ })
+ class CustomControlWritebackTest implements ControlValueAccessor {
+ value = '';
+
+ private onChangeFn?: (value: string) => void;
+
+ writeValue(newValue: string): void {
+ writeValues.push(newValue);
+ this.value = newValue;
+ }
+
+ registerOnChange(fn: (value: string) => void): void {
+ this.onChangeFn = fn;
+ }
+
+ registerOnTouched(fn: () => void): void {}
+
+ onInput(newValue: string) {
+ this.value = newValue;
+ this.onChangeFn?.(newValue);
+ }
+ }
+
+ @Component({
+ imports: [CustomControlWritebackTest, FormField],
+ template: ``,
+ })
+ class TestCmp {
+ readonly f = form(signal('initial'), (p) => {
+ debounce(p, 'blur');
+ });
+ }
+
+ const fixture = act(() => TestBed.createComponent(TestCmp));
+
+ const debugEl = fixture.debugElement.query(
+ (el) => el.componentInstance instanceof CustomControlWritebackTest,
+ );
+
+ const cvaInstance = debugEl.componentInstance as CustomControlWritebackTest;
+
+ writeValues = [];
+
+ act(() => cvaInstance.onInput('updated'));
+
+ expect(cvaInstance.value).toBe('updated');
+
+ expect(writeValues).toEqual([]);
+ });
+
describe('properties', () => {
describe('disabled', () => {
it('should bind to directive input', () => {