diff --git a/blog/2025-10-signal-forms-part1/README.md b/blog/2025-10-signal-forms-part1/README.md index 92f1ab83..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 @@ -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 92255125..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 @@ -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', @@ -225,20 +225,18 @@ 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'; -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 : { - 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. @@ -285,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', @@ -339,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(), @@ -380,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, @@ -412,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)); }); ``` @@ -423,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.fieldOf(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 b782c446..179d8431 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-11 +lastModified: 2025-11-13 keywords: - Angular - Signals @@ -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); }); ```