Sitelet https://github.com/angular/angular/commit/376b71ea1c33522d99a91bd6f8a6779e0ca752e0
Skip to content

Commit 376b71e

Browse files
MeAkibatscott
authored andcommitted
docs(forms): document what required() considers empty
The `required()` validator treats `null`, `undefined`, `''`, `false` and `NaN` as empty, but the API docs never defined "empty" at all and the validation guide listed only `null` and `''`. (cherry picked from commit 467b37b)
1 parent 4b5571c commit 376b71e

3 files changed

Lines changed: 78 additions & 12 deletions

File tree

‎adev/src/content/guide/forms/signals/validation.md‎

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,22 @@ export class RegistrationComponent {
110110
}
111111
```
112112

113-
A field is considered "empty" when:
113+
A field is considered "empty" when its value is one of the following, and non-empty for every other
114+
value — including `0` and the empty array `[]`:
114115

115-
| Condition | Example |
116-
| ------------------------ | ------- |
117-
| Value is `null` | `null`, |
118-
| Value is an empty string | `''` |
116+
| Condition | Example |
117+
| ------------------------ | ----------- |
118+
| Value is `null` | `null` |
119+
| Value is `undefined` | `undefined` |
120+
| Value is an empty string | `''` |
121+
| Value is `false` | `false` |
122+
| Value is `NaN` | `NaN` |
123+
124+
The last two are worth calling out:
125+
126+
- `false` is empty to follow the native semantics of `required` on `<input type="checkbox">`, where
127+
an unchecked box fails validation.
128+
- `NaN` is empty because it is usually the result of a parsing error, and is not a valid number.
119129

120130
For conditional requirements, use the `when` option:
121131

@@ -130,7 +140,7 @@ registrationForm = form(this.registrationModel, (schemaPath) => {
130140

131141
The validation rule only runs when the `when` function returns `true`.
132142

133-
Note: `required` treats an empty array as present (valid), so use [`minLength()`](#minlength-and-maxlength) to enforce a minimum number of array items; it treats `false` as missing (invalid), matching `<input type="checkbox" required>`.
143+
Note: `required` treats an empty array as present (valid), so use [`minLength()`](#minlength-and-maxlength) to enforce a minimum number of array items.
134144

135145
### email()
136146

@@ -505,9 +515,7 @@ interface User {
505515
lastName: string;
506516
}
507517
508-
@Component({
509-
/* ... */
510-
})
518+
@Component({/* ... */})
511519
export class UserFormComponent {
512520
readonly userModel = model<User>({
513521
firstName: '',
@@ -750,9 +758,7 @@ import {Component, computed, signal} from '@angular/core';
750758
import {form, FormField, validateStandardSchema} from '@angular/forms/signals';
751759
import z from 'zod';
752760
753-
@Component({
754-
/* ... */
755-
})
761+
@Component({/* ... */})
756762
export class DynamicSchema {
757763
model = signal({document: '', type: 'dni'});
758764

‎packages/forms/signals/src/api/rules/validation/required.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import {requiredError} from './validation_errors';
1717
* This function can only be called on any type of path.
1818
* In addition to binding a validator, this function adds `REQUIRED` property to the field.
1919
*
20+
* A value is considered empty when it is `null`, `undefined`, the empty string `''`, `false`, or
21+
* `NaN`. Every other value is considered non-empty, including `0` and the empty array `[]` — use
22+
* [`minLength()`](api/forms/signals/minLength) to require a minimum number of items in an array.
23+
*
24+
* `false` is empty to follow the native semantics of `required` on `<input type="checkbox">`, where
25+
* an unchecked box fails validation. `NaN` is empty because it is usually the result of a parsing
26+
* error, and is not a valid number.
27+
*
2028
* @param path Path of the field to validate
2129
* @param config Optional, allows providing any of the following options:
2230
* - `message`: A user-facing message for the error.

‎packages/forms/signals/test/node/api/validators/required.spec.ts‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,58 @@ import {form, required} from '../../../../public_api';
1212
import {requiredError} from '../../../../src/api/rules/validation/validation_errors';
1313

1414
describe('required validator', () => {
15+
// Documented on `required()` and in guide/forms/signals/validation#required. `false` follows the
16+
// native semantics of `required` on `<input type="checkbox">`; `NaN` is not a valid number.
17+
describe('emptiness', () => {
18+
it('treats null, empty string, false and NaN as empty', () => {
19+
const model = signal<{
20+
nullable: string | null;
21+
text: string;
22+
checkbox: boolean;
23+
num: number;
24+
}>({nullable: null, text: '', checkbox: false, num: Number.NaN});
25+
const f = form(
26+
model,
27+
(p) => {
28+
required(p.nullable);
29+
required(p.text);
30+
required(p.checkbox);
31+
required(p.num);
32+
},
33+
{injector: TestBed.inject(Injector)},
34+
);
35+
36+
expect(f.nullable().errors()).toEqual([requiredError({fieldTree: f.nullable})]);
37+
expect(f.text().errors()).toEqual([requiredError({fieldTree: f.text})]);
38+
expect(f.checkbox().errors()).toEqual([requiredError({fieldTree: f.checkbox})]);
39+
expect(f.num().errors()).toEqual([requiredError({fieldTree: f.num})]);
40+
});
41+
42+
it('treats 0, an empty array and other filled values as non-empty', () => {
43+
const model = signal<{
44+
zero: number;
45+
list: string[];
46+
checkbox: boolean;
47+
text: string;
48+
}>({zero: 0, list: [], checkbox: true, text: 'a'});
49+
const f = form(
50+
model,
51+
(p) => {
52+
required(p.zero);
53+
required(p.list);
54+
required(p.checkbox);
55+
required(p.text);
56+
},
57+
{injector: TestBed.inject(Injector)},
58+
);
59+
60+
expect(f.zero().errors()).toEqual([]);
61+
expect(f.list().errors()).toEqual([]);
62+
expect(f.checkbox().errors()).toEqual([]);
63+
expect(f.text().errors()).toEqual([]);
64+
});
65+
});
66+
1567
it('returns required Error when the value is not present', () => {
1668
const cat = signal({name: ''});
1769
const f = form(

0 commit comments

Comments
 (0)