Skip to content

Signal Forms interop: transformedValue parse errors reach a FormValueControl one change detection late under ngModel and [formControl] #71127

Description

@Cynthion

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);
}
  1. Type x into the input.
  2. control.errors is { parse: { kind: 'parse', message: 'Not a number.' } }, but the control still renders errors this control sees: 0.
  3. 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.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: formsgemini-triagedLabel noting that an issue has been triaged by gemini

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions