Repository navigation
Description
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.
CompatValidationStateinpackages/forms/signals/compat/src/compat_validation_state.ts:
errorSummaryanderrors.CompatValidationErrorinpackages/forms/signals/src/compat/validation_errors.ts.errorSummary()on any form with compat fields, including its DOM-order sort.- Consumers that read
error.fieldTreeon compat errors.
Tradeoffs of the fix.
- The summary fix needs
fieldTreefirst. On the client, the root summary sorts errors by DOM
position througherror.fieldTree(), so adding compat errors without it would throw a
TypeErroronce the summary has two or more errors. - Setting
fieldTreeinCompatValidationStatewith the existingaddDefaultField()helper keeps
the publicCompatValidationErrorclass unchanged. Adding a constructor parameter instead
changes public API. - Nested errors of a
FormGrouporFormArrayvalue all point at the compat field, because
nested controls have noFieldTreeof 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.
- Triage PR with the recommended fix, design decisions, and questions: test(forms): add repro specs for compat errorSummary #71630
- Triage notes:
observed output, root cause, and the sort hazard. CompatValidationState.errorSummary,
FieldValidationState.errorSummary,
CompatValidationError,
addDefaultField(),
DOM-order sort.
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=allPlease 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.
Metadata
Metadata
Assignees
Type
Projects
- StatusShow more project fieldsNo status
Activity