Skip to content

Commit dd0e15f

Browse files
authored
signalforms: updates for 21.0.0-rc.2, fieldTreeOf (#46)
* signalforms: updates for 21.0.0-rc.2, fieldTreeOf * schemaPath * datum
1 parent c6c239c commit dd0e15f

File tree

3 files changed

+44
-42
lines changed

3 files changed

+44
-42
lines changed

blog/2025-10-signal-forms-part1/README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: "Angular Signal Forms Part 1: Getting Started with the Basics"
33
author: Danny Koppenhagen and Ferdinand Malcher
44
mail: dannyferdigravatar@fmalcher.de # Gravatar
55
published: 2025-10-13
6-
lastModified: 2025-11-09
6+
lastModified: 2025-11-13
77
keywords:
88
- Angular
99
- Signals
@@ -368,7 +368,7 @@ The next part will cover more advanced and complex scenarios – so stay tuned!
368368

369369
Signal Forms use the `schema()` function to define validation rules.
370370
Angular comes with some very common rules by default, such as `required` and `minLength`.
371-
The provided `fieldPath` parameter allows us to navigate through the form structure and apply validation rules to specific fields.
371+
The provided `schemaPath` parameter allows us to navigate through the form structure and apply validation rules to specific fields.
372372

373373
```typescript
374374
import {
@@ -378,9 +378,9 @@ import {
378378
minLength,
379379
} from '@angular/forms/signals';
380380

381-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
382-
required(fieldPath.username, { message: 'Username is required' });
383-
minLength(fieldPath.username, 3, {
381+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
382+
required(schemaPath.username, { message: 'Username is required' });
383+
minLength(schemaPath.username, 3, {
384384
message: 'A username must be at least 3 characters long',
385385
});
386386
// ...
@@ -414,31 +414,31 @@ Signal Forms provide several built-in validation functions:
414414

415415
| Validator | Description | Example |
416416
| -------------------------------- | ----------------------------------------------------------- | ----------------------------------------------- |
417-
| `required(field, opts)` | Field must be filled. For boolean values, checks for `true` | `required(fieldPath.username)` |
418-
| `minLength(field, length, opts)` | Minimum character count | `minLength(fieldPath.username, 3)` |
419-
| `maxLength(field, length, opts)` | Maximum character count | `maxLength(fieldPath.username, 10)` |
420-
| `min(field, value, opts)` | Minimum numeric value | `min(fieldPath.age, 18)` |
421-
| `max(field, value, opts)` | Maximum numeric value | `max(fieldPath.age, 120)` |
422-
| `email(field, opts)` | Valid email address format | `email(fieldPath.email)` |
423-
| `pattern(field, regex, opts)` | Regular expression match | `pattern(fieldPath.username, /^[a-zA-Z0-9]+$/)` |
417+
| `required(field, opts)` | Field must be filled. For boolean values, checks for `true` | `required(schemaPath.username)` |
418+
| `minLength(field, length, opts)` | Minimum character count | `minLength(schemaPath.username, 3)` |
419+
| `maxLength(field, length, opts)` | Maximum character count | `maxLength(schemaPath.username, 10)` |
420+
| `min(field, value, opts)` | Minimum numeric value | `min(schemaPath.age, 18)` |
421+
| `max(field, value, opts)` | Maximum numeric value | `max(schemaPath.age, 120)` |
422+
| `email(field, opts)` | Valid email address format | `email(schemaPath.email)` |
423+
| `pattern(field, regex, opts)` | Regular expression match | `pattern(schemaPath.username, /^[a-zA-Z0-9]+$/)` |
424424

425425
Each validator function accepts an optional `opts` parameter where you can specify a custom error message.
426426
We can use this message later to display it in the component template.
427427

428428
A validation schema for our registration form could look like this:
429429

430430
```typescript
431-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
431+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
432432
// Username validation
433-
required(fieldPath.username, { message: 'Username is required' });
434-
minLength(fieldPath.username, 3, { message: 'A username must be at least 3 characters long' });
435-
maxLength(fieldPath.username, 12, { message: 'A username can be max. 12 characters long' });
433+
required(schemaPath.username, { message: 'Username is required' });
434+
minLength(schemaPath.username, 3, { message: 'A username must be at least 3 characters long' });
435+
maxLength(schemaPath.username, 12, { message: 'A username can be max. 12 characters long' });
436436

437437
// Age validation
438-
min(fieldPath.age, 18, { message: 'You must be >=18 years old.' });
438+
min(schemaPath.age, 18, { message: 'You must be >=18 years old.' });
439439

440440
// Terms and conditions
441-
required(fieldPath.agreeToTermsAndConditions, {
441+
required(schemaPath.agreeToTermsAndConditions, {
442442
message: 'You must agree to the terms and conditions.',
443443
});
444444
});

blog/2025-10-signal-forms-part2/README.md

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: 'Angular Signal Forms Part 2: Advanced Validation and Schema Patterns'
33
author: Danny Koppenhagen and Ferdinand Malcher
44
mail: dannyferdigravatar@fmalcher.de # Gravatar
55
published: 2025-10-15
6-
lastModified: 2025-11-09
6+
lastModified: 2025-11-13
77
keywords:
88
- Angular
99
- Signals
@@ -87,7 +87,7 @@ We can directly use this path and pass it into our `email()` validation function
8787
import { /* ... */, applyEach, email } from '@angular/forms/signals';
8888

8989
// ...
90-
applyEach(fieldPath.email, (emailPath) => {
90+
applyEach(schemaPath.email, (emailPath) => {
9191
email(emailPath, { message: 'E-Mail format is invalid' });
9292
});
9393
```
@@ -139,10 +139,10 @@ The `message` is optional, but it is recommended to provide a user-friendly mess
139139
```typescript
140140
import { /* ... */, validate } from '@angular/forms/signals';
141141

142-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
142+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
143143
// ...
144144
// E-Mail validation
145-
validate(fieldPath.email, (ctx) =>
145+
validate(schemaPath.email, (ctx) =>
146146
!ctx.value().some((e) => e)
147147
? {
148148
kind: 'atLeastOneEmail',
@@ -225,27 +225,29 @@ Errors can be assigned to individual fields (`pw1`) or to the grouping node (`pa
225225
For validations that depend on multiple fields, Signal Forms provide a `validateTree()` function.
226226
The `ChildFieldContext` passed to the callback gives access to the entire subtree, allowing us to compare values of different fields.
227227
An interesting aspect of this function is that we can assign errors to any field within the subtree.
228-
Access to fields is possible through the `fieldOf()` method.
229-
We can also use the `valueOf()` method to access values of other fields in the tree.
230228

231229
```typescript
232230
import { /* ... */, validateTree } from '@angular/forms/signals';
233231

234-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
232+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
235233
// ...
236234
// Password confirmation validation
237-
validateTree(fieldPath.password, (ctx) => {
235+
validateTree(schemaPath.password, (ctx) => {
238236
return ctx.value().pw2 === ctx.value().pw1
239237
? undefined
240238
: {
241-
field: ctx.fieldOf(fieldPath.password.pw2), // assign the error to the second password field
239+
field: ctx.field.pw2, // assign the error to the second password field
242240
kind: 'confirmationPassword',
243241
message: 'The entered password must match with the one specified in "Password" field',
244242
};
245243
});
246244
});
247245
```
248246

247+
Apart from this example, access to other fields is possible through the `fieldTreeOf()` method.
248+
We can also use `valueOf()` to access values of other fields in the tree.
249+
250+
249251
> `validateTree()` defines custom validation logic for a group of related fields. It returns a validation error or `undefined` if the values are valid.
250252
251253

@@ -285,14 +287,14 @@ We use the `validate()` function to check if a topic is selected.
285287
```typescript
286288
import { /* ... */, applyWhen } from '@angular/forms/signals';
287289

288-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
290+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
289291
// ...
290292
// Only validate newsletter topics if user subscribed to newsletter
291293
applyWhen(
292-
fieldPath,
294+
schemaPath,
293295
(ctx) => ctx.value().newsletter,
294-
(fieldPathWhenTrue) => {
295-
validate(fieldPathWhenTrue.newsletterTopics, (ctx) =>
296+
(schemaPathWhenTrue) => {
297+
validate(schemaPathWhenTrue.newsletterTopics, (ctx) =>
296298
!ctx.value().length
297299
? {
298300
kind: 'noTopicSelected',
@@ -339,10 +341,10 @@ We also have to handle errors in the asynchronous operation, which can be done u
339341
import { /* ... */, resource } from '@angular/core';
340342
import { /* ... */, validateAsync } from '@angular/forms/signals';
341343

342-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
344+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
343345
// ...
344346
// Check username availability on the server
345-
validateAsync(fieldPath.username, {
347+
validateAsync(schemaPath.username, {
346348
// Reactive parameters for the async operation
347349
params: ({ value }) => value(),
348350

@@ -380,7 +382,7 @@ If we enter `johndoe`, the validation will fail, and the corresponding error mes
380382
For HTTP endpoints, you can also use the simpler `validateHttp()` function:
381383

382384
```typescript
383-
validateHttp(fieldPath.username, {
385+
validateHttp(schemaPath.username, {
384386
request: (ctx) => `/api/check?username=${ctx.value()}`,
385387
errors: (taken: boolean) =>
386388
taken ? ({ kind: 'userExists', message: 'Username already taken' }) : undefined,
@@ -412,24 +414,24 @@ The corresponding field will change its state when the condition is met.
412414
```typescript
413415
import { /* ... */, disabled, readonly, hidden } from '@angular/forms/signals';
414416

415-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
417+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
416418
// ...
417419
// Disable newsletter topics when newsletter is unchecked
418-
disabled(fieldPath.newsletterTopics, (ctx) => !ctx.valueOf(fieldPath.newsletter));
420+
disabled(schemaPath.newsletterTopics, (ctx) => !ctx.valueOf(schemaPath.newsletter));
419421
});
420422
```
421423

422424
Here are some more examples of how to use `readonly` and `hidden`:
423425

424426
```typescript
425427
// make `someField` read-only if `otherField` has the value 'someValue'
426-
readonly(fieldPath.someField, (ctx) => ctx.valueOf(fieldPath.otherField) === 'someValue');
428+
readonly(schemaPath.someField, (ctx) => ctx.valueOf(schemaPath.otherField) === 'someValue');
427429

428430
// make `someField` read-only if `otherField` is invalid
429-
readonly(fieldPath.someField, (ctx) => !ctx.fieldOf(fieldPath.otherField)().valid());
431+
readonly(schemaPath.someField, (ctx) => !ctx.fieldTreeOf(schemaPath.otherField)().valid());
430432

431433
// hide `someField` if the value of `otherField` is falsy
432-
hidden(fieldPath.someField, (ctx) => !ctx.valueOf(fieldPath.otherField));
434+
hidden(schemaPath.someField, (ctx) => !ctx.valueOf(schemaPath.otherField));
433435
```
434436

435437
Disabled and read-only states are automatically reflected in the template when using the `[field]` directive.

blog/2025-10-signal-forms-part3/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: 'Angular Signal Forms Part 3: Child Forms and Custom UI Controls'
33
author: Danny Koppenhagen and Ferdinand Malcher
44
mail: dannyferdigravatar@fmalcher.de # Gravatar
55
published: 2025-10-20
6-
lastModified: 2025-11-11
6+
lastModified: 2025-11-13
77
keywords:
88
- Angular
99
- Signals
@@ -193,11 +193,11 @@ Next, we use the `apply()` function within our main schema to integrate the chil
193193
// registrsation-form.ts
194194
import { GenderIdentity, IdentityForm, identitySchema, initialGenderIdentityState } from '../identity-form/identity-form';
195195

196-
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
196+
export const registrationSchema = schema<RegisterFormData>((schemaPath) => {
197197
// ...
198198

199199
// apply child schema for identity checks
200-
apply(fieldPath.identity, identitySchema);
200+
apply(schemaPath.identity, identitySchema);
201201
});
202202
```
203203

0 commit comments

Comments
 (0)