Skip to content

Commit 09b3971

Browse files
authored
signalforms2: update for 21-rc.0: validateAsync, no customError (#43)
1 parent b17d495 commit 09b3971

File tree

1 file changed

+26
-28
lines changed

1 file changed

+26
-28
lines changed

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

Lines changed: 26 additions & 28 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-10-16
6+
lastModified: 2025-10-30
77
keywords:
88
- Angular
99
- Signals
@@ -132,22 +132,22 @@ The callback function provides access to the field state, represented as a `Chil
132132
This object can be used to access the `value` signal and read the current value of the email array.
133133

134134
Since the value is a `string[]`, we can use `Array.some()` to check if at least one non-empty email address exists.
135-
To produce an error, we use the `customError()` function to create a validation error object with a `kind` and a `message`.
135+
To produce an error, we return a validation error object with a `kind` and a `message`.
136136
If no error occurs, we return `undefined`.
137137
The `message` is optional, but it is recommended to provide a user-friendly message that can be displayed in the UI later.
138138

139139
```typescript
140-
import { /* ... */, validate, customError } from '@angular/forms/signals';
140+
import { /* ... */, validate } from '@angular/forms/signals';
141141

142142
export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
143143
// ...
144144
// E-Mail validation
145145
validate(fieldPath.email, (ctx) =>
146146
!ctx.value().some((e) => e)
147-
? customError({
147+
? {
148148
kind: 'atLeastOneEmail',
149149
message: 'At least one E-Mail address must be added',
150-
})
150+
}
151151
: undefined
152152
);
153153
});
@@ -237,11 +237,11 @@ export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
237237
validateTree(fieldPath.password, (ctx) => {
238238
return ctx.value().pw2 === ctx.value().pw1
239239
? undefined
240-
: customError({
240+
: {
241241
field: ctx.fieldOf(fieldPath.password.pw2), // assign the error to the second password field
242242
kind: 'confirmationPassword',
243243
message: 'The entered password must match with the one specified in "Password" field',
244-
});
244+
};
245245
});
246246
});
247247
```
@@ -294,10 +294,10 @@ export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
294294
(fieldPathWhenTrue) => {
295295
validate(fieldPathWhenTrue.newsletterTopics, (ctx) =>
296296
!ctx.value().length
297-
? customError({
297+
? {
298298
kind: 'noTopicSelected',
299299
message: 'Select at least one newsletter topic',
300-
})
300+
}
301301
: undefined
302302
);
303303
}
@@ -332,7 +332,8 @@ export class RegistrationService {
332332
To perform async validation, we can use the `validateAsync()` function in our schema.
333333
The `params` property allows us to pick the required data from the field state, again represented as a `ChildFieldContext` object.
334334
The `factory` property is a function that creates a resource that actually performs the async operation.
335-
Finally, the `errors` function maps the value of the resource to a validation error, just as we did before with custom synchronous validations.
335+
Finally, the `onSuccess` function maps the value of the resource to a validation error, just as we did before with custom synchronous validations.
336+
We also have to handle errors in the asynchronous operation, which can be done using the `onError` property. If the validation fails due to a server error, we ignore it by returning `undefined`.
336337

337338
```typescript
338339
import { /* ... */, resource } from '@angular/core';
@@ -357,14 +358,15 @@ export const registrationSchema = schema<RegisterFormData>((fieldPath) => {
357358
},
358359

359360
// Map the result to validation errors
360-
errors: (result) => {
361+
onSuccess: (result) => {
361362
return result
362-
? customError({
363+
? {
363364
kind: 'userExists',
364365
message: 'The username you entered was already taken',
365-
})
366+
}
366367
: undefined;
367368
},
369+
onError: () => undefined
368370
});
369371
});
370372
```
@@ -381,7 +383,7 @@ For HTTP endpoints, you can also use the simpler `validateHttp()` function:
381383
validateHttp(fieldPath.username, {
382384
request: (ctx) => `/api/check?username=${ctx.value()}`,
383385
errors: (taken: boolean) =>
384-
taken ? customError({ kind: 'userExists', message: 'Username already taken' }) : undefined,
386+
taken ? ({ kind: 'userExists', message: 'Username already taken' }) : undefined,
385387
});
386388
```
387389

@@ -452,15 +454,15 @@ When using the `submit()` function, we can return an array of validation errors
452454
The helper type `WithField` ensures that each error contains a reference to the field it belongs to.
453455

454456
```typescript
455-
import { /* ... */, WithField, CustomValidationError, ValidationError } from '@angular/forms/signals';
457+
import { /* ... */, WithField, ValidationErrorWithField } from '@angular/forms/signals';
456458

457459
export class RegistrationForm {
458460
// ...
459461
protected async submitForm(e: Event) {
460462
e.preventDefault();
461463

462464
await submit(this.registrationForm, async (form) => {
463-
const errors: WithField<CustomValidationError | ValidationError>[] = [];
465+
const errors: ValidationErrorWithField[] = [];
464466

465467
try {
466468
await this.#registrationService.registerUser(form().value);
@@ -469,24 +471,20 @@ export class RegistrationForm {
469471
} catch (e) {
470472
// Add server-side errors
471473
errors.push(
472-
customError({
474+
{
473475
field: form, // form-level error
474-
error: {
475-
kind: 'serverError',
476-
message: 'Registration failed. Please try again.',
477-
},
478-
})
476+
kind: 'serverError',
477+
message: 'Registration failed. Please try again.',
478+
}
479479
);
480480

481481
// Or assign to specific field
482482
errors.push(
483-
customError({
483+
{
484484
field: form.username,
485-
error: {
486-
kind: 'serverValidation',
487-
message: 'Username is not available.',
488-
},
489-
})
485+
kind: 'serverValidation',
486+
message: 'Username is not available.',
487+
}
490488
);
491489
}
492490

0 commit comments

Comments
 (0)