@@ -3,7 +3,7 @@ title: 'Angular Signal Forms Part 2: Advanced Validation and Schema Patterns'
33author : Danny Koppenhagen and Ferdinand Malcher
44mail : dannyferdigravatar@fmalcher.de # Gravatar
55published : 2025-10-15
6- lastModified : 2025-11-09
6+ lastModified : 2025-11-13
77keywords :
88 - Angular
99 - Signals
@@ -87,7 +87,7 @@ We can directly use this path and pass it into our `email()` validation function
8787import { /* ... */ , 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
140140import { /* ... */ , 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
225225For validations that depend on multiple fields, Signal Forms provide a ` validateTree() ` function.
226226The ` ChildFieldContext ` passed to the callback gives access to the entire subtree, allowing us to compare values of different fields.
227227An 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
232230import { /* ... */ , 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
286288import { /* ... */ , 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
339341import { /* ... */ , resource } from ' @angular/core' ;
340342import { /* ... */ , 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
380382For 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
413415import { /* ... */ , 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
422424Here 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
435437Disabled and read-only states are automatically reflected in the template when using the ` [field] ` directive.
0 commit comments