Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validators do not always work the expected way #54214

Open
Almamu opened this issue Feb 2, 2024 · 1 comment · May be fixed by #55866
Open

Validators do not always work the expected way #54214

Almamu opened this issue Feb 2, 2024 · 1 comment · May be fixed by #55866

Comments

@Almamu
Copy link

Almamu commented Feb 2, 2024

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

forms

Is this a regression?

No

Description

We've come across to what I think is a bug in form validation in reactive forms. Stackblitz is kind of self-explanatory, but to sum it up, when creating FormControl (that is not visible right away on the view), validation works differently depending on how validators are passed onto the control (simple validator vs array of validators).

If you open the stackblitz, and right away click on the buttons you'll see that one of the forms shows an alert and the other doesn't. The only difference between these two is the validatorOrOpts param used to create the controls.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/stackblitz-starters-378jka?file=src%2Fmain.ts

Please provide the exception or error you saw

Both forms should either be valid or invalid (based on the expected behaviour from the Angular team) instead of varying based on how the ValidatorFn are passed.

Please provide the environment you discovered this bug in (run ng version)

Angular CLI: 17.1.2
Node: 18.18.0
Package Manager: npm 10.2.3
OS: linux x64

Angular: 17.1.2
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1701.2
@angular-devkit/build-angular   17.1.2
@angular-devkit/core            17.1.2
@angular-devkit/schematics      17.1.2
@schematics/angular             17.1.2
rxjs                            7.8.1
typescript                      5.3.3
zone.js                         0.14.3

Anything else?

After further testing looks like changing the validator's return from

  static test(control: AbstractControl): ValidationErrors | null {
    return {
      ...Validators.maxLength(10)(control),
    };
  }

to

  static test(control: AbstractControl): ValidationErrors | null {
    return Validators.maxLength(10)(control);
  }

solves the issue, but looks like a random difference in validation :/

In our case we're using the first to return all the errors in one (as we have some complex validators that might need of all the errors to properly build feedback for the user).

@ngbot ngbot bot added this to the needsTriage milestone Feb 2, 2024
@garrettld
Copy link
Contributor

garrettld commented Feb 2, 2024

The root cause here is that when a control has an array of validators, they go through the compose function, and when there's only a single validator (not wrapped in an array) the compose code path is skipped:

this._composedValidatorFn = coerceToValidator(this._rawValidators);

function coerceToValidator(validator: ValidatorFn|ValidatorFn[]|null): ValidatorFn|null {
return Array.isArray(validator) ? composeValidators(validator) : validator || null;
}

As you found out, the compose path uses Object.keys().length to convert an empty object to null, which causes the array version to be treated as valid, and the non-array version is treated as invalid because it lacks that conversion.

I think the question here is whether an empty object should be:

  1. invalid
  2. valid
  3. avoided

Currently it's both (1) and (2)

JeanMeche added a commit to JeanMeche/angular that referenced this issue May 17, 2024
In some cases the validator could return an empty object while having all validators returning null
This commit makes it more consistant by treating empty error objects as valid controls.

fixes angular#54214
@JeanMeche JeanMeche linked a pull request May 17, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants