dynamic-forms / Interface

AsyncCustomValidator

Generic types:TValue TParams TResult

Async custom validator configuration using Angular's resource-based API

Angular's validateAsync uses the resource API for async validation, which provides better integration with Signal Forms lifecycle management.

Structure:

  • params: Function that computes params from field context
  • factory: Function that creates a ResourceRef from params signal
  • onSuccess: Maps successful resource result to validation errors
  • onError: Optional handler for resource errors (network failures, etc.)

Use Cases:

  • Database lookups via services with resource API
  • Complex async business logic with Angular resources
  • Any async operation using Angular's resource pattern

Note: For HTTP validation, prefer HttpCustomValidator which provides a simpler API specifically designed for HTTP requests.

Properties

NameTypeDescription
factory
r
(params: Signal<TParams | undefined>) => ResourceRef<TResult | undefined>

Function that creates a ResourceRef from the params signal. The resource will be automatically managed by Angular's lifecycle.

onError
r
((error: unknown, ctx: RootFieldContext<TValue>) => ValidationError | ValidationError[] | null) | undefined

Optional error handler for resource errors (network failures, etc.). Return null to ignore the error, or ValidationError to display it to the user.

onSuccess
r
((result: TResult, ctx: RootFieldContext<TValue>) => TreeValidationResult) | undefined

Optional function to map successful resource result to validation errors. Return null if validation passes, or ValidationError/ValidationError[] if validation fails.

params
r
(ctx: RootFieldContext<TValue>, config?: Record<string, unknown> | undefined) => TParams

Function that receives field context and returns resource params. Params will be tracked as a signal and trigger resource reload when changed.

Example usage

Database Lookup with Resource

const checkUsernameAvailable: AsyncCustomValidator<string> = {
  params: (ctx) => ({ username: ctx.value() }),
  factory: (params) => {
    const injector = inject(Injector);
    return resource({
      request: () => params(),
      loader: ({ request }) => {
        if (!request?.username) return null;
        const service = injector.get(UserService);
        return firstValueFrom(service.checkAvailability(request.username));
      }
    });
  },
  onSuccess: (result, ctx) => {
    if (!result) return null;
    return result.available ? null : { kind: 'usernameTaken' };
  },
  onError: (error, ctx) => {
    console.error('Availability check failed:', error);
    return null; // Don't block form on network errors
  }
};