From b5cfeedd6c190a5a583543105783ddb42e1447ed Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Thu, 13 Nov 2025 17:46:31 +0100 Subject: [PATCH 1/3] signalforms: updates for 21.0.0-rc.2, fieldTreeOf --- blog/2025-10-signal-forms-part2/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/blog/2025-10-signal-forms-part2/README.md b/blog/2025-10-signal-forms-part2/README.md index 92255125..dba2bbf9 100644 --- a/blog/2025-10-signal-forms-part2/README.md +++ b/blog/2025-10-signal-forms-part2/README.md @@ -225,8 +225,6 @@ Errors can be assigned to individual fields (`pw1`) or to the grouping node (`pa For validations that depend on multiple fields, Signal Forms provide a `validateTree()` function. The `ChildFieldContext` passed to the callback gives access to the entire subtree, allowing us to compare values of different fields. An interesting aspect of this function is that we can assign errors to any field within the subtree. -Access to fields is possible through the `fieldOf()` method. -We can also use the `valueOf()` method to access values of other fields in the tree. ```typescript import { /* ... */, validateTree } from '@angular/forms/signals'; @@ -238,7 +236,7 @@ export const registrationSchema = schema((fieldPath) => { return ctx.value().pw2 === ctx.value().pw1 ? undefined : { - field: ctx.fieldOf(fieldPath.password.pw2), // assign the error to the second password field + field: ctx.field.pw2, // assign the error to the second password field kind: 'confirmationPassword', message: 'The entered password must match with the one specified in "Password" field', }; @@ -246,6 +244,10 @@ export const registrationSchema = schema((fieldPath) => { }); ``` +Apart from this example, access to other fields is possible through the `fieldTreeOf()` method. +We can also use `valueOf()` to access values of other fields in the tree. + + > `validateTree()` defines custom validation logic for a group of related fields. It returns a validation error or `undefined` if the values are valid. @@ -426,7 +428,7 @@ Here are some more examples of how to use `readonly` and `hidden`: readonly(fieldPath.someField, (ctx) => ctx.valueOf(fieldPath.otherField) === 'someValue'); // make `someField` read-only if `otherField` is invalid -readonly(fieldPath.someField, (ctx) => !ctx.fieldOf(fieldPath.otherField)().valid()); +readonly(fieldPath.someField, (ctx) => !ctx.fieldTreeOf(fieldPath.otherField)().valid()); // hide `someField` if the value of `otherField` is falsy hidden(fieldPath.someField, (ctx) => !ctx.valueOf(fieldPath.otherField)); From 8190d45d0eeaec0c72ad69c899aa87be877b33c9 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Thu, 13 Nov 2025 18:06:17 +0100 Subject: [PATCH 2/3] schemaPath --- blog/2025-10-signal-forms-part1/README.md | 34 +++++++++++------------ blog/2025-10-signal-forms-part2/README.md | 34 +++++++++++------------ blog/2025-10-signal-forms-part3/README.md | 4 +-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/blog/2025-10-signal-forms-part1/README.md b/blog/2025-10-signal-forms-part1/README.md index 92f1ab83..7a940abd 100644 --- a/blog/2025-10-signal-forms-part1/README.md +++ b/blog/2025-10-signal-forms-part1/README.md @@ -368,7 +368,7 @@ The next part will cover more advanced and complex scenarios – so stay tuned! Signal Forms use the `schema()` function to define validation rules. Angular comes with some very common rules by default, such as `required` and `minLength`. -The provided `fieldPath` parameter allows us to navigate through the form structure and apply validation rules to specific fields. +The provided `schemaPath` parameter allows us to navigate through the form structure and apply validation rules to specific fields. ```typescript import { @@ -378,9 +378,9 @@ import { minLength, } from '@angular/forms/signals'; -export const registrationSchema = schema((fieldPath) => { - required(fieldPath.username, { message: 'Username is required' }); - minLength(fieldPath.username, 3, { +export const registrationSchema = schema((schemaPath) => { + required(schemaPath.username, { message: 'Username is required' }); + minLength(schemaPath.username, 3, { message: 'A username must be at least 3 characters long', }); // ... @@ -414,13 +414,13 @@ Signal Forms provide several built-in validation functions: | Validator | Description | Example | | -------------------------------- | ----------------------------------------------------------- | ----------------------------------------------- | -| `required(field, opts)` | Field must be filled. For boolean values, checks for `true` | `required(fieldPath.username)` | -| `minLength(field, length, opts)` | Minimum character count | `minLength(fieldPath.username, 3)` | -| `maxLength(field, length, opts)` | Maximum character count | `maxLength(fieldPath.username, 10)` | -| `min(field, value, opts)` | Minimum numeric value | `min(fieldPath.age, 18)` | -| `max(field, value, opts)` | Maximum numeric value | `max(fieldPath.age, 120)` | -| `email(field, opts)` | Valid email address format | `email(fieldPath.email)` | -| `pattern(field, regex, opts)` | Regular expression match | `pattern(fieldPath.username, /^[a-zA-Z0-9]+$/)` | +| `required(field, opts)` | Field must be filled. For boolean values, checks for `true` | `required(schemaPath.username)` | +| `minLength(field, length, opts)` | Minimum character count | `minLength(schemaPath.username, 3)` | +| `maxLength(field, length, opts)` | Maximum character count | `maxLength(schemaPath.username, 10)` | +| `min(field, value, opts)` | Minimum numeric value | `min(schemaPath.age, 18)` | +| `max(field, value, opts)` | Maximum numeric value | `max(schemaPath.age, 120)` | +| `email(field, opts)` | Valid email address format | `email(schemaPath.email)` | +| `pattern(field, regex, opts)` | Regular expression match | `pattern(schemaPath.username, /^[a-zA-Z0-9]+$/)` | Each validator function accepts an optional `opts` parameter where you can specify a custom error message. We can use this message later to display it in the component template. @@ -428,17 +428,17 @@ We can use this message later to display it in the component template. A validation schema for our registration form could look like this: ```typescript -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // Username validation - required(fieldPath.username, { message: 'Username is required' }); - minLength(fieldPath.username, 3, { message: 'A username must be at least 3 characters long' }); - maxLength(fieldPath.username, 12, { message: 'A username can be max. 12 characters long' }); + required(schemaPath.username, { message: 'Username is required' }); + minLength(schemaPath.username, 3, { message: 'A username must be at least 3 characters long' }); + maxLength(schemaPath.username, 12, { message: 'A username can be max. 12 characters long' }); // Age validation - min(fieldPath.age, 18, { message: 'You must be >=18 years old.' }); + min(schemaPath.age, 18, { message: 'You must be >=18 years old.' }); // Terms and conditions - required(fieldPath.agreeToTermsAndConditions, { + required(schemaPath.agreeToTermsAndConditions, { message: 'You must agree to the terms and conditions.', }); }); diff --git a/blog/2025-10-signal-forms-part2/README.md b/blog/2025-10-signal-forms-part2/README.md index dba2bbf9..a9085e01 100644 --- a/blog/2025-10-signal-forms-part2/README.md +++ b/blog/2025-10-signal-forms-part2/README.md @@ -87,7 +87,7 @@ We can directly use this path and pass it into our `email()` validation function import { /* ... */, applyEach, email } from '@angular/forms/signals'; // ... -applyEach(fieldPath.email, (emailPath) => { +applyEach(schemaPath.email, (emailPath) => { email(emailPath, { message: 'E-Mail format is invalid' }); }); ``` @@ -139,10 +139,10 @@ The `message` is optional, but it is recommended to provide a user-friendly mess ```typescript import { /* ... */, validate } from '@angular/forms/signals'; -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // ... // E-Mail validation - validate(fieldPath.email, (ctx) => + validate(schemaPath.email, (ctx) => !ctx.value().some((e) => e) ? { kind: 'atLeastOneEmail', @@ -229,10 +229,10 @@ An interesting aspect of this function is that we can assign errors to any field ```typescript import { /* ... */, validateTree } from '@angular/forms/signals'; -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // ... // Password confirmation validation - validateTree(fieldPath.password, (ctx) => { + validateTree(schemaPath.password, (ctx) => { return ctx.value().pw2 === ctx.value().pw1 ? undefined : { @@ -287,14 +287,14 @@ We use the `validate()` function to check if a topic is selected. ```typescript import { /* ... */, applyWhen } from '@angular/forms/signals'; -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // ... // Only validate newsletter topics if user subscribed to newsletter applyWhen( - fieldPath, + schemaPath, (ctx) => ctx.value().newsletter, - (fieldPathWhenTrue) => { - validate(fieldPathWhenTrue.newsletterTopics, (ctx) => + (schemaPathWhenTrue) => { + validate(schemaPathWhenTrue.newsletterTopics, (ctx) => !ctx.value().length ? { kind: 'noTopicSelected', @@ -341,10 +341,10 @@ We also have to handle errors in the asynchronous operation, which can be done u import { /* ... */, resource } from '@angular/core'; import { /* ... */, validateAsync } from '@angular/forms/signals'; -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // ... // Check username availability on the server - validateAsync(fieldPath.username, { + validateAsync(schemaPath.username, { // Reactive parameters for the async operation params: ({ value }) => value(), @@ -382,7 +382,7 @@ If we enter `johndoe`, the validation will fail, and the corresponding error mes For HTTP endpoints, you can also use the simpler `validateHttp()` function: ```typescript -validateHttp(fieldPath.username, { +validateHttp(schemaPath.username, { request: (ctx) => `/api/check?username=${ctx.value()}`, errors: (taken: boolean) => taken ? ({ kind: 'userExists', message: 'Username already taken' }) : undefined, @@ -414,10 +414,10 @@ The corresponding field will change its state when the condition is met. ```typescript import { /* ... */, disabled, readonly, hidden } from '@angular/forms/signals'; -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // ... // Disable newsletter topics when newsletter is unchecked - disabled(fieldPath.newsletterTopics, (ctx) => !ctx.valueOf(fieldPath.newsletter)); + disabled(schemaPath.newsletterTopics, (ctx) => !ctx.valueOf(schemaPath.newsletter)); }); ``` @@ -425,13 +425,13 @@ Here are some more examples of how to use `readonly` and `hidden`: ```typescript // make `someField` read-only if `otherField` has the value 'someValue' -readonly(fieldPath.someField, (ctx) => ctx.valueOf(fieldPath.otherField) === 'someValue'); +readonly(schemaPath.someField, (ctx) => ctx.valueOf(schemaPath.otherField) === 'someValue'); // make `someField` read-only if `otherField` is invalid -readonly(fieldPath.someField, (ctx) => !ctx.fieldTreeOf(fieldPath.otherField)().valid()); +readonly(schemaPath.someField, (ctx) => !ctx.fieldTreeOf(schemaPath.otherField)().valid()); // hide `someField` if the value of `otherField` is falsy -hidden(fieldPath.someField, (ctx) => !ctx.valueOf(fieldPath.otherField)); +hidden(schemaPath.someField, (ctx) => !ctx.valueOf(schemaPath.otherField)); ``` Disabled and read-only states are automatically reflected in the template when using the `[field]` directive. diff --git a/blog/2025-10-signal-forms-part3/README.md b/blog/2025-10-signal-forms-part3/README.md index 5a684b18..af6e5c7a 100644 --- a/blog/2025-10-signal-forms-part3/README.md +++ b/blog/2025-10-signal-forms-part3/README.md @@ -193,11 +193,11 @@ Next, we use the `apply()` function within our main schema to integrate the chil // registrsation-form.ts import { GenderIdentity, IdentityForm, identitySchema, initialGenderIdentityState } from '../identity-form/identity-form'; -export const registrationSchema = schema((fieldPath) => { +export const registrationSchema = schema((schemaPath) => { // ... // apply child schema for identity checks - apply(fieldPath.identity, identitySchema); + apply(schemaPath.identity, identitySchema); }); ``` From 30c95f630f1852a2638577a90c396eef900bebb8 Mon Sep 17 00:00:00 2001 From: Ferdinand Malcher Date: Thu, 13 Nov 2025 20:02:05 +0100 Subject: [PATCH 3/3] datum --- blog/2025-10-signal-forms-part1/README.md | 2 +- blog/2025-10-signal-forms-part2/README.md | 2 +- blog/2025-10-signal-forms-part3/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/blog/2025-10-signal-forms-part1/README.md b/blog/2025-10-signal-forms-part1/README.md index 7a940abd..a304e6cf 100644 --- a/blog/2025-10-signal-forms-part1/README.md +++ b/blog/2025-10-signal-forms-part1/README.md @@ -3,7 +3,7 @@ title: "Angular Signal Forms Part 1: Getting Started with the Basics" author: Danny Koppenhagen and Ferdinand Malcher mail: dannyferdigravatar@fmalcher.de # Gravatar published: 2025-10-13 -lastModified: 2025-11-09 +lastModified: 2025-11-13 keywords: - Angular - Signals diff --git a/blog/2025-10-signal-forms-part2/README.md b/blog/2025-10-signal-forms-part2/README.md index a9085e01..23a93a93 100644 --- a/blog/2025-10-signal-forms-part2/README.md +++ b/blog/2025-10-signal-forms-part2/README.md @@ -3,7 +3,7 @@ title: 'Angular Signal Forms Part 2: Advanced Validation and Schema Patterns' author: Danny Koppenhagen and Ferdinand Malcher mail: dannyferdigravatar@fmalcher.de # Gravatar published: 2025-10-15 -lastModified: 2025-11-09 +lastModified: 2025-11-13 keywords: - Angular - Signals diff --git a/blog/2025-10-signal-forms-part3/README.md b/blog/2025-10-signal-forms-part3/README.md index af6e5c7a..c1033ebf 100644 --- a/blog/2025-10-signal-forms-part3/README.md +++ b/blog/2025-10-signal-forms-part3/README.md @@ -3,7 +3,7 @@ title: 'Angular Signal Forms Part 3: Child Forms and Custom UI Controls' author: Danny Koppenhagen and Ferdinand Malcher mail: dannyferdigravatar@fmalcher.de # Gravatar published: 2025-10-20 -lastModified: 2025-11-09 +lastModified: 2025-11-13 keywords: - Angular - Signals