Skip to content

Signal forms: compatForm() FormControl errors are missing from errorSummary() #71634

Open

Description

@Flarette

Which @angular/* package(s) are the source of the bug?

forms

Is this a regression?

No

Description

In short. A form built with compatForm() can be invalid because of a FormControl validator,
while its errorSummary() is empty. The errors also have no fieldTree, although their type says
they do.

Why it matters. errorSummary() is how an app lists every error at once, for example next to
the submit button. With compat fields, submit() is blocked but the list is empty, so the user
can't tell what to fix. Code that follows an error to its field, such as
error.fieldTree().focusBoundControl(), throws on a compat error.

Failure mode in user code.

@Component({
  imports: [FormField],
  template: `
    <input [formField]="f.name" />
    <button (click)="save()">Save</button>
    @if (showErrors()) {
      <ul>
        @for (error of f().errorSummary(); track error) {
          <li>{{ error.kind }}</li>
        }
      </ul>
    }
  `,
})
export class Signup {
  readonly showErrors = signal(false);
  readonly model = signal({
    name: new FormControl('', {nonNullable: true, validators: Validators.required}),
  });
  readonly f = compatForm(this.model);

  save() {
    submit(this.f, {
      action: async () => saveSignup(this.model().name.value),
      onInvalid: () => this.showErrors.set(true),
    });
  }
}

Starting state: the FormControl value is ''. f.name().errors() holds one required error,
and f().invalid() is true.

Current behavior:

1. User clicks Save   the action doesn't run; onInvalid shows the list
                      f().errorSummary() → []
                      the list is empty

Expected behavior:

1. User clicks Save   the action doesn't run; onInvalid shows the list
                      f().errorSummary() → [{kind: 'required', fieldTree: f.name}]
                      the list shows "required"

Assumptions: expected vs current.

Expected Current
errorSummary() holds "the errors of the field and its descendants", as its doc comment says. A compat field's errorSummary is a shared empty signal.
invalid() and errorSummary() agree: an invalid form has at least one error in its summary. f().invalid() is true while f().errorSummary() is [].
Every error typed ValidationError.WithFieldTree has a fieldTree that points at its field, as plain errors do. CompatValidationError declares fieldTree but never sets it.

Affected areas.

  • CompatValidationState in packages/forms/signals/compat/src/compat_validation_state.ts:
    errorSummary and errors.
  • CompatValidationError in packages/forms/signals/src/compat/validation_errors.ts.
  • errorSummary() on any form with compat fields, including its DOM-order sort.
  • Consumers that read error.fieldTree on compat errors.

Tradeoffs of the fix.

  • The summary fix needs fieldTree first. On the client, the root summary sorts errors by DOM
    position through error.fieldTree(), so adding compat errors without it would throw a
    TypeError once the summary has two or more errors.
  • Setting fieldTree in CompatValidationState with the existing addDefaultField() helper keeps
    the public CompatValidationError class unchanged. Adding a constructor parameter instead
    changes public API.
  • Nested errors of a FormGroup or FormArray value all point at the compat field, because
    nested controls have no FieldTree of their own.
  • A compat field bound with [formControl] instead of [formField] has no bound element, so its
    errors sort to the end of the summary.

Related. #71633, where compat fields drop parse errors, changes the same class.

Details.

Please provide a link to a minimal reproduction of the bug

compat_error_summary.spec.ts
in the triage PR. Each spec asserts the expected behavior, so 2 of them fail today:

pnpm bazel test //packages/forms/signals/test/node:test --test_output=all

Please provide the exception or error you saw

No error is thrown. The repro specs report:

root errorSummary(): Expected $.length = 0 to equal 1.
fieldTree of the compat field error: Expected undefined to be Function.

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/node:test.
The affected source files are unchanged on main at 3e173fc844 (2026-10-24).

Anything else?

I searched for an existing issue or PR and found none.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions