dynamic-forms / Class

FunctionRegistryService

Decorators:@Injectable

Registry service for custom functions and validators

This service maintains four separate registries:

  1. Custom Functions - For conditional expressions (when/readonly/disabled)

    • Used in: when conditions, readonly logic, disabled logic
    • Return type: any value (typically boolean)
    • Example: isAdult: (ctx) => ctx.age >= 18
  2. Custom Validators - For synchronous validation using Angular's public FieldContext API

    • Used in: validators array on fields
    • Return type: ValidationError | ValidationError[] | null
    • Example: noSpaces: (ctx) => ctx.value().includes(' ') ? { kind: 'noSpaces' } : null
  3. Async Validators - For asynchronous validation (debouncing, database lookups, etc.)

    • Used in: validators array on fields with type 'customAsync'
    • Return type: Observable<ValidationError | ValidationError[] | null>
    • Example: checkUsername: (ctx) => userService.checkAvailability(ctx.value())
  4. HTTP Validators - For HTTP-based validation with automatic request cancellation

    • Used in: validators array on fields with type 'customHttp'
    • Configuration object with url, method, mapResponse, etc.
    • Example: { url: '/api/check', method: 'GET', mapResponse: ... }

Methods

clearAll()

Clear everything (functions and all validators)

Presentation
clearAll(): void;
Returns
void

clearAsyncValidators()

Clear all async validators

Presentation
clearAsyncValidators(): void;
Returns
void

clearCustomFunctions()

Clear all custom functions and their scopes

Presentation
clearCustomFunctions(): void;
Returns
void

clearHttpValidators()

Clear all HTTP validators

Presentation
clearHttpValidators(): void;
Returns
void

clearValidators()

Clear all validators

Presentation
clearValidators(): void;
Returns
void

getAsyncValidator()

Get an async validator by name

Presentation
getAsyncValidator(name: string): AsyncCustomValidator<unknown, unknown, unknown> | undefined;
Parameters
NameTypeDescription
name
string
Returns
AsyncCustomValidator<unknown, unknown, unknown> | undefined

getCustomFunctions()

Get all custom functions as an object

Presentation
getCustomFunctions(): Record<string, CustomFunction>;
Returns
Record<string, CustomFunction>

getCustomFunctionScope()

Get the scope of a registered custom function.

Presentation
getCustomFunctionScope(name: string): CustomFunctionScope | undefined;
Parameters
NameTypeDescription
name
string

The function name

Returns
CustomFunctionScope | undefined -

The function scope, or undefined if not registered

getHttpValidator()

Get an HTTP validator by name

Presentation
getHttpValidator(name: string): HttpCustomValidator<unknown, unknown> | undefined;
Parameters
NameTypeDescription
name
string
Returns
HttpCustomValidator<unknown, unknown> | undefined

getValidator()

Get a validator by name

Presentation
getValidator(name: string): CustomValidator | undefined;
Parameters
NameTypeDescription
name
string
Returns
CustomValidator | undefined

isFieldScopedFunction()

Check if a custom function is field-scoped (no cross-field dependencies).

Presentation
isFieldScopedFunction(name: string): boolean;
Parameters
NameTypeDescription
name
string

The function name

Returns
boolean -

true if the function is field-scoped, false if form-scoped or not registered

registerAsyncValidator()

Register an async validator using Angular's public validateAsync() API

Async validators return Observables for asynchronous validation logic. Use for debouncing, database lookups, or complex async business logic.

Presentation
registerAsyncValidator(name: string, fn: AsyncCustomValidator<unknown, unknown, unknown>): void;
Parameters
NameTypeDescription
name
string

Unique identifier for the async validator

fn
AsyncCustomValidator<unknown, unknown, unknown>

Async validator function (ctx, params?) => Observable<ValidationError | ValidationError[] | null>

Returns
void
Example usage

Debounced Username Check

registry.registerAsyncValidator('checkUsernameAvailable', (ctx) => {
  const username = ctx.value();
  return of(username).pipe(
    debounceTime(300),
    switchMap(name => userService.checkAvailability(name)),
    map(available => available ? null : { kind: 'usernameTaken' })
  );
});

Async Cross-Field Validation

registry.registerAsyncValidator('validatePasswordStrength', (ctx) => {
  const password = ctx.value();
  const email = ctx.valueOf('email' as any);
  return passwordService.checkStrength(password, email).pipe(
    map(result => result.strong ? null : { kind: 'weakPassword' })
  );
});

registerCustomFunction()

Register a custom function for conditional expressions

Custom functions are used for control flow logic (when/readonly/disabled), NOT for validation. They return any value, typically boolean.

Presentation
registerCustomFunction(name: string, fn: CustomFunction, options?: CustomFunctionOptions | undefined): void;
Parameters
NameTypeDescription
name
string

Unique identifier for the function

fn
CustomFunction

Function that receives EvaluationContext and returns any value

options
CustomFunctionOptions | undefined

Optional configuration for the function

Returns
void
Example usage
// Form-level function (default) - may access other fields
registry.registerCustomFunction('isAdult', (ctx) => ctx.formValue.age >= 18);

// Field-level function - only uses current field value (performance optimization)
registry.registerCustomFunction('isEmpty', (ctx) => !ctx.fieldValue, { scope: 'field' });

registerHttpValidator()

Register an HTTP validator configuration using Angular's public validateHttp() API

HTTP validators provide optimized HTTP validation with automatic request cancellation, caching, and debouncing. Preferred over AsyncCustomValidator for HTTP requests.

Presentation
registerHttpValidator(name: string, config: HttpCustomValidator<unknown, unknown>): void;
Parameters
NameTypeDescription
name
string

Unique identifier for the HTTP validator

config
HttpCustomValidator<unknown, unknown>

HTTP validator configuration object

Returns
void
Example usage

Username Availability Check

registry.registerHttpValidator('checkUsername', {
  url: (ctx) => `/api/users/check-username?username=${encodeURIComponent(ctx.value())}`,
  method: 'GET',
  mapResponse: (response, ctx) => {
    return response.available ? null : { kind: 'usernameTaken' };
  }
});

POST Request with Body

registry.registerHttpValidator('validateAddress', {
  url: '/api/validate-address',
  method: 'POST',
  body: (ctx) => ({
    street: ctx.valueOf('street' as any),
    city: ctx.valueOf('city' as any),
    zipCode: ctx.value()
  }),
  mapResponse: (response) => {
    return response.valid ? null : { kind: 'invalidAddress' };
  },
  debounceTime: 500
});

registerValidator()

Register a custom validator using Angular's public FieldContext API

Validators receive the full FieldContext, allowing access to:

  • Current field value: ctx.value()
  • Field state: ctx.state (errors, touched, dirty, etc.)
  • Other field values: ctx.valueOf(path) (public API!)
  • Other field states: ctx.stateOf(path)
  • Parameters from JSON configuration via second argument
Presentation
registerValidator(name: string, fn: CustomValidator): void;
Parameters
NameTypeDescription
name
string

Unique identifier for the validator

fn
CustomValidator

Validator function (ctx, params?) => ValidationError | ValidationError[] | null

Returns
void
Example usage

Single Field Validation

registry.registerValidator('noSpaces', (ctx) => {
  const value = ctx.value();
  if (typeof value === 'string' && value.includes(' ')) {
    return { kind: 'noSpaces' };
  }
  return null;
});

Cross-Field Validation (Public API)

registry.registerValidator('lessThan', (ctx, params) => {
  const value = ctx.value();
  const compareToPath = params?.field as string;

  // Use valueOf() to access other fields - public API!
  const otherValue = ctx.valueOf(compareToPath as any);

  if (otherValue !== undefined && value >= otherValue) {
    return { kind: 'notLessThan' };
  }
  return null;
});

Multiple Errors (Cross-Field Validation)

registry.registerValidator('validateDateRange', (ctx) => {
  const errors: ValidationError[] = [];
  const startDate = ctx.valueOf('startDate' as any);
  const endDate = ctx.valueOf('endDate' as any);

  if (!startDate) errors.push({ kind: 'startDateRequired' });
  if (!endDate) errors.push({ kind: 'endDateRequired' });
  if (startDate && endDate && startDate > endDate) {
    errors.push({ kind: 'invalidDateRange' });
  }

  return errors.length > 0 ? errors : null;
});

setAsyncValidators()

Set async validators from a config object Only updates validators if their references have changed

Presentation
setAsyncValidators(asyncValidators: Record<string, AsyncCustomValidator<unknown, unknown, unknown>> | undefined): void;
Parameters
NameTypeDescription
asyncValidators
Record<string, AsyncCustomValidator<unknown, unknown, unknown>> | undefined

Object mapping validator names to async validator configs

Returns
void

setHttpValidators()

Set HTTP validators from a config object Only updates validators if their references have changed

Presentation
setHttpValidators(httpValidators: Record<string, HttpCustomValidator<unknown, unknown>> | undefined): void;
Parameters
NameTypeDescription
httpValidators
Record<string, HttpCustomValidator<unknown, unknown>> | undefined

Object mapping validator names to HTTP validator configs

Returns
void

setValidators()

Set validators from a config object Only updates validators if their references have changed

Presentation
setValidators(validators: Record<string, CustomValidator> | undefined): void;
Parameters
NameTypeDescription
validators
Record<string, CustomValidator> | undefined

Object mapping validator names to validator functions

Returns
void

Example usage

// Register a custom function for expressions
registry.registerCustomFunction('isAdult', (ctx) => ctx.age >= 18);

// Use in field configuration
{
  key: 'alcoholPreference',
  when: { function: 'isAdult' }
}

// Register a custom validator
registry.registerValidator('noSpaces', (ctx) => {
  const value = ctx.value();
  return typeof value === 'string' && value.includes(' ')
    ? { kind: 'noSpaces' }
    : null;
});

// Use in field configuration
{
  key: 'username',
  validators: [{ type: 'custom', functionName: 'noSpaces' }],
  validationMessages: {
    noSpaces: 'Spaces are not allowed'
  }
}