Repository navigation
Description
Which @angular/* package(s) are the source of the bug?
forms
Is this a regression?
Yes
Description
In short. When one field is bound to several controls and one of them has a parse error, CVA and
native hosts receive that parse error in their errors too. So does NgControl.errors. Each
control should receive only the parse errors it produced, as custom controls do today.
Why it matters. Binding one field to two controls is a common pattern, for example a slider
next to a number input. Typing 5e into the number input then makes the slider show the number
input's parse error as its own. Parse errors became common in v22: native inputs produce them, and so do
legacy CVA validators.
Failure mode in user code.
@Component({
selector: 'app-slider',
template: `
<input type="range" min="0" max="100" />
@for (error of errors(); track error) {
<small>{{ error.message ?? error.kind }}</small>
}
`,
providers: [{provide: NG_VALUE_ACCESSOR, useExisting: Slider, multi: true}],
})
export class Slider implements ControlValueAccessor {
readonly errors = input<readonly ValidationError[]>([]);
// writeValue, registerOnChange, and registerOnTouched omitted.
}
@Component({
imports: [FormField, Slider],
template: `
<app-slider [formField]="f.volume" />
<input type="number" [formField]="f.volume" />
`,
})
export class Mixer {
readonly f = form(signal({volume: 50}));
}Starting state: f.volume().value() is 50, both controls show 50, and neither has errors.
Current behavior:
1. User types "5e" into the number input
number input FormField.errors() → [{kind: 'parse'}]
slider errors input → [{kind: 'parse'}] the slider shows the error
both invalid input → true
Expected behavior:
1. User types "5e" into the number input
number input FormField.errors() → [{kind: 'parse'}]
slider errors input → [] the slider shows nothing
both invalid input → true
History. The filter existed for every host in v21.2.0-next.1
(ebae211add).
The refactor in v21.2.0-next.2
(a67e00741c)
kept it only for custom controls. The leak became visible in v22.0.0-next.0, when native inputs
started producing parse errors
(22afbb2f36).
Assumptions: expected vs current.
| Expected | Current |
|---|---|
Each control receives only the parse errors of its own binding. ebae211add states this, and custom controls still work this way. |
CVA and native hosts receive the field's unfiltered errors(). |
inject(NgControl).errors reflects the binding it was injected for. |
It converts the field's unfiltered errors(). |
invalid is field-level, so every control on the field receives true. |
Same. This doesn't change. |
Affected areas.
readFieldStateBindingValue()inpackages/forms/signals/src/directive/bindings.ts, and its
callers incontrol_cva.ts,control_native.ts, andcontrol_custom.ts.InteropNgControl.errorsinpackages/forms/signals/src/controls/interop_ng_control.ts, and how
FormFieldcreates it.- Consumers: directives with an
errorsinput on native elements, CVA components with anerrors
input, and components that readNgControl.errors. - Not affected:
FormField.errors(), which is already filtered, and custom controls.
Tradeoffs of the fix.
- Routing every host through one helper that reads
FormField.errorschanges an internal
function's signature and its three callers. Copying the special case into two more files is a
smaller diff, but three copies are how the behavior drifted. - Filtering
NgControl.errorsmeans passing the binding's errors intoInteropNgControl, which is
an internal constructor change. It can land with the fix or later. - A control can receive
invalid: truewith emptyerrors. Custom controls already get that
today, so UI that shows invalid styling without a message stays as it is.
Details.
- Triage PR with the recommended fix, design decisions, and questions: test(forms): add repro specs for per-control parse errors #71629
- Triage notes:
observed output, history, and theNgControlpath. - Custom host, filtered,
CVA host,
native host,
readFieldStateBindingValue(),
InteropNgControl.errors.
Please provide a link to a minimal reproduction of the bug
form_field_errors_per_binding.spec.ts
in the triage PR. Both specs assert the expected behavior and fail today. The web test runner
ignores --filter, so this runs the whole Chromium suite:
pnpm bazel test //packages/forms/signals/test/web:test_chromium --test_output=allPlease provide the exception or error you saw
No error is thrown. The repro specs report:
Unexpected $[1].errorsInput[0] = 'parse from native (bad input)' in array.
Unexpected $[2].errorsInput[0] = 'parse from native (bad input)' in array.
Unexpected $[1].errors[0] = 'parse' in array.
Please provide the environment you discovered this bug in (run ng version)
angular/angular main at ce30bb0889 (2026-10-16), Bazel target
//packages/forms/signals/test/web:test_chromium. The affected source files are unchanged on main at
3e173fc844 (2026-10-24). First affected prerelease: v21.2.0-next.2.
Anything else?
I searched for an existing issue or PR and found none.
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsNo status
Activity
Akxe commentedon Oct 26, 2026
I do think that there is a need to be able to set new value to the route resource. For me, it is an optimistic update before I reload the whole resource.