Which @angular/* package(s) are the source of the bug?
forms
Is this a regression?
No
Description
A FormValueControl that parses its text with transformedValue reports a parse error to ngModel and [formControl] through NgControl.setParseErrorSource(). The FormControl gets the error, but the control's own errors input does not follow until something else checks the host. Under [formField] it arrives at once.
@Component({
selector: 'app-number-input',
template: `
<input [value]="text()" (input)="text.set($any($event.target).value)" />
<p>errors this control sees: {{ errors().length }}</p>
`,
})
export class NumberInput implements FormValueControl<number | null> {
readonly value = model<number | null>(null);
readonly errors = input<readonly ValidationError.WithOptionalFieldTree[]>([]);
protected readonly text = transformedValue(this.value, {
parse: (text: string): ParseResult<number | null> => {
if (text === '') return { value: null };
const parsed = Number(text);
return Number.isNaN(parsed) ? { error: { kind: 'parse', message: 'Not a number.' } } : { value: parsed };
},
format: (value: number | null) => (value === null ? '' : String(value)),
});
}
@Component({
imports: [ReactiveFormsModule, NumberInput],
template: `
<app-number-input [formControl]="control" />
<!-- Any event handler marks the host for check. -->
<button (click)="(0)">Check again</button>
`,
})
export class App {
readonly control = new FormControl<number | null>(null);
}
- Type
x into the input.
control.errors is { parse: { kind: 'parse', message: 'Not a number.' } }, but the control still renders errors this control sees: 0.
- Click Check again: it now renders
errors this control sees: 1.
The same happens with [(ngModel)]. Bound with [formField], the control renders 1 right after step 1.
The cause looks like the effect in setParseErrorSource(). It calls this.control?.updateValueAndValidity({ emitEvent: false }), so statusChanges never emits and the markForCheck subscription that setupCustomControl() sets up never fires. ngControlUpdate(), which forwards errors to the control, then waits for the next unrelated check of the host. Marking the host for check from that effect would fix it.
Minimal reproduction
The two components above, zoneless, nothing else.
Exception or error
None: the control renders stale errors.
Environment
Angular CLI 22.2.0, Angular 22.2.0, Node.js 26.7.0, darwin arm64. setParseErrorSource() is the same in 22.2.1, 22.3.0-next.0 and on main.
Anything else?
The workaround in the control: a markForCheck() after the next render whenever its parse errors change.
Which @angular/* package(s) are the source of the bug?
forms
Is this a regression?
No
Description
A
FormValueControlthat parses its text withtransformedValuereports a parse error tongModeland[formControl]throughNgControl.setParseErrorSource(). TheFormControlgets the error, but the control's ownerrorsinput does not follow until something else checks the host. Under[formField]it arrives at once.xinto the input.control.errorsis{ parse: { kind: 'parse', message: 'Not a number.' } }, but the control still renderserrors this control sees: 0.errors this control sees: 1.The same happens with
[(ngModel)]. Bound with[formField], the control renders1right after step 1.The cause looks like the effect in
setParseErrorSource(). It callsthis.control?.updateValueAndValidity({ emitEvent: false }), sostatusChangesnever emits and themarkForChecksubscription thatsetupCustomControl()sets up never fires.ngControlUpdate(), which forwardserrorsto the control, then waits for the next unrelated check of the host. Marking the host for check from that effect would fix it.Minimal reproduction
The two components above, zoneless, nothing else.
Exception or error
None: the control renders stale errors.
Environment
Angular CLI 22.2.0, Angular 22.2.0, Node.js 26.7.0, darwin arm64.
setParseErrorSource()is the same in 22.2.1, 22.3.0-next.0 and onmain.Anything else?
The workaround in the control: a
markForCheck()after the next render whenever its parse errors change.