Skip to main content

API overview

Use this page to find the API that matches what you are trying to model or integrate. Import public symbols from @ngblocks/form-nodes; do not import internal files or deep package paths.

If you already have a concrete failure or unexpected state, use the symptom-oriented Troubleshooting guide.

Using IntelliSense examples​

Editor hovers include focused examples for individual options and operations. Import the Form Nodes symbols used in each example from @ngblocks/form-nodes; Angular imports are shown explicitly. Examples include their own model or component setup, and value comments describe the expected result. Child operations use their model path, such as profile.name.set(...). Examples on aggregate operations call the form, group, or array itself, so you can see which part of the tree is affected. Direct validator registration and resolved compositions have separate examples. Collection-query examples use object templates and predicates on children, such as user.username() === 'Ada'.

Option descriptions explain defaults, inheritance, and accepted input forms. For broader workflows, follow the references below, especially node value views, interaction state, and asynchronous validation.

🧭 Choose an API by task​

I want to…Start withDetails
Model one logical valuefield()field() reference
Type a node input or reusable utilityAnyNode, DynamicNode, or a concrete node typeNode types reference
Organize unrelated standalone nodes without aggregate behaviorPlain JavaScript objectCreating nodes
Define a submission workflow boundaryform()form() reference
Choose the default field nullability for an applicationcreateFormPrimitives()createFormPrimitives() reference
Check an inferred aggregate against a named value modelFormValueContract<TValue>FormValueContract reference
Extract the value type of any nodeFormNodeValue<typeof node>FormNodeValue reference
Model a dynamic ordered collection of independent nodesarray()array() reference
Give an object branch its own options without creating a submission workflowExplicit group()group() reference
Choose between a structured field, form, or arrayβ€”Choosing a primitive
Design a large domain-oriented form treeModeling boundaries and lifecycleForm modeling patterns
Add a built-in validation rulerequired, email, min, and othersBuilt-in validators
Understand validation across every nodevalidators, errors, and statusValidation reference
Author a reusable synchronous rulevalidator()validator() reference
Run Promise- or Observable-based validationasyncValidator()asyncValidator() reference
Bind a node to an Angular controlFormNodeDirective and [formNode]FormNodeDirective binding API
Submit through a native <form>FormNodeDirectiveForm submission
Configure validator messages through Angular DIprovideFormNodesConfig()provideFormNodesConfig()
Configure process-wide messages and binding defaultsconfigureGlobalFormNodes()configureGlobalFormNodes()
Add reactive status classes to every bindingprovideFormNodesConfig()provideFormNodesConfig()
Inspect the API shared by all nodesAnyNode, DynamicNode, and NodeApiNode API
Test a form model or Angular bindingPublic node API and, when needed, TestBedTesting forms
Use Angular Material controlsFormNodeDirective with Material's normal modulesAngular Material integration
Use PrimeNG controlsFormNodeDirective with PrimeNG's normal modulesPrimeNG integration

🧩 Modeling primitives​

β—† field()​

Creates one leaf node. Its value can be a string, number, date, object, array, or any other application type. Nullability follows the generic and initial value.

const myForm = form({
displayName: field(''),
birthDate: field<Date>(),
selectedRoles: field<string[]>([]),
});

An array-valued field is appropriate when one control owns the complete array, such as a multi-select. It intentionally has no per-item nodes or structural operations.

Main exports: field, FieldNode, FieldApi, and FieldOptions.

Use createFormPrimitives({ nullable: true }) to add null to application-scoped fields and shorthands. Explicit field.strict() and field.nullable() calls take precedence.

β—† form()​

Creates the typed object tree that owns a submission workflow. It has the same structural behavior as a group plus onSubmit configuration and submit(). Root application workflows normally start with form(); explicit nested forms are reserved for independent subflows.

Values such as name: '', age: 23, birthday: new Date(), value: null, value: undefined, and roles: ['admin'] are concise field definitions. Object literals remain group definitions. An array value always becomes a field; only an explicit array(...) creates a dynamic collection of item nodes. Ordinary functions and non-plain object instances become concise fields too.

Main exports: form, FormNode, FormApi, FormOptions, FormValue, FormNodeValue, FormValueContract, FormSet, FormPatch.

β—† array()​

Creates a dynamic collection by cloning one node template or invoking a factory. Use it when items need independent bindings, paths, validation, interaction state, or structural operations.

const myForm = form({
contacts: array({
label: field(''),
email: field(''),
}, 2),
});

Main exports: array, ArrayNode, ArrayApi, ArrayOptions, ArrayValue, ArraySet, ArrayPatch, ArrayItems, ArrayIndexes, and ArrayItemWithParent.

β—† Explicit group()​

Plain nested objects already create structural groups and are the preferred way to model ordinary fixed branches:

const myForm = form({
displayName: field(''),
address: {
city: field(''),
country: field(''),
},
});

Use explicit group({...}, options) only when an object branch needs its own aggregate validators, state options, or message configuration without becoming a submission workflow. Main exports: group, GroupNode, GroupApi, GroupOptions, GroupValue, GroupSet, and GroupPatch.

βœ… Validation​

β—† Synchronous validation​

Pass built-in or custom validators to any node. validator<TValue>() supplies an explicit reusable authoring type but does not wrap or alter the callback at runtime.

const positive = validator<number | null>(({ value }) => {
const number = value();

return number !== null && number <= 0
? { kind: 'positive', actual: number }
: null;
});

const myForm = form({
quantity: field<number>(null, [required, positive]),
});

Frequently used types include ValidationError, ValidationResult, ValidationStatus, ValidatorContext, ValidatorSource, Validators, and the extensible ValidationErrorMap.

β—† Asynchronous validation​

asyncValidator() marks asynchronous work explicitly so the node owns pending state, debounce, cancellation, dependency tracking, and stale-result protection.

const myForm = form({
username: field('', [
asyncValidator(({ value, abortSignal }) => {
return checkUsername(value(), abortSignal).then(available =>
available ? null : { kind: 'usernameTaken' },
);
}, {
debounce: 300,
}),
]),
});

Related types include AsyncValidator, AsyncValidatorContext, AsyncValidatorOptions, ParameterizedAsyncValidatorConfig, and ParameterizedAsyncValidatorContext.

β—† Validator messages​

Message configuration follows this precedence, from highest to lowest:

  1. Validator-local message.
  2. Closest form or array validatorMessages catalog.
  3. Closest createFormPrimitives() validator-message default.
  4. Closest provideFormNodesConfig() provider.
  5. configureGlobalFormNodes().
  6. Built-in English message.

Use provider or form scopes for request-specific SSR locales. Process-wide configuration is better suited to non-Angular usage or one immutable application default.

const restoreMessages = configureGlobalFormNodes({
validatorMessages: {
required: 'This value is required.',
min: ({ min }) => `The minimum value is ${min}.`,
},
});

// Restore the previous global catalog when a temporary scope ends.
restoreMessages();

In an Angular application, use provideFormNodesConfig() when the catalog should follow an application, route, environment injector, or SSR request scope:

provideFormNodesConfig({
validatorMessages: () => ({
required: () => translations().required,
}),
});

πŸ”Œ Angular integration​

β—† [formNode]​

Import FormNodeDirective into a standalone component and bind nodes directly:

@Component({
imports: [FormNodeDirective],
template: `
<input [formNode]="form.displayName" />
`,
})
export class ProfileEditor {
form = form({
displayName: field(''),
});
}

The binding supports native controls, ControlValueAccessor, Angular-compatible signal models, and input/output control pairs (the latter require experimental bindInputOutputPairs: true). Its public query type exposes node(), errors(), element, injector, focus(), flush(), and reset().

Main exports: FormNodeDirective, FormNodeBinding, and FORM_NODE. One FormNodeDirective import supports native controls, custom controls, and native form roots.

β—† Custom-control and binding configuration​

APIPurpose
provideFormNodesConfig()Configures validator messages, custom-control inputs, and reactive CSS classes.
ANGULAR_FORMS_STATUS_CLASSESOptional Angular Forms-compatible validity and interaction class preset.
provideFormNodePassThrough()Marks a directive or host directive that delegates formNode.
FormNodeValueControl<T>Signal control whose main model is value.
FormNodeCheckboxControlBoolean signal control whose main model is checked.
useFormNodeState<T>()Reads normalized state from [formNode], [formField], [formControl], formControlName, or ngModel.
useClosestFormState()Shared submission history with optional reactive Form Nodes API access.
ControlState<T>Source-neutral control signals, formSubmitted(), and complete nearest form state returned by useFormNodeState().
FormNodeErrorsError messages with touch-or-submit visibility, custom templates, and optional height animation.
FormNodeErrorsContextFirst message, visible messages, and error details for a nested #message template.
ControlStateDisabledReasonSource-neutral disabled reason containing an optional message.

See the useFormNodeState() reference for its complete signal surface, source precedence, normalization rules, lifecycle, and examples for every supported binding API.

Configure binding classes once in the application providers for the common application-wide case:

import type { ApplicationConfig } from '@angular/core';
import { ANGULAR_FORMS_STATUS_CLASSES, provideFormNodesConfig } from '@ngblocks/form-nodes';

export const appConfig: ApplicationConfig = {
providers: [
provideFormNodesConfig({
classes: ANGULAR_FORMS_STATUS_CLASSES,
}),
],
};

The configuration applies to [formNode] bindings created below that injector. Put the same provider in a route, component, or NgModule providers array when only that subtree should use it; the nearest provider wins. No automatic classes are installed unless this provider is configured.

Most ordinary signal components and CVAs require no explicit provider. See Custom controls before choosing a lower-level integration API.

⚑ Shared node state​

Every field, form, and array exposes common reactive state:

AreaMain members
ValueNode call, value(), value.control(), set(), update(), reset()
Validationerrors(), allErrors(), getError(), valid(), invalid(), pending()
Interactiontouched(), dirty(), their complements, and marking methods
Availabilitydisabled(), readonly(), hidden(), their complements, reasons, and actions
Treeform(), root(), parent(), path(), keyInParent(), and object-node add(), get(), remove()
Controlsdebouncing(), flush(), focus()

Call the node itself for its committed value and use direct members for normal application code. Use .$api for generic infrastructure or child-name collisions.

πŸ“ Importing types​

Use type-only imports when a symbol is used only by TypeScript:

import { field, form, FormNodeDirective } from '@ngblocks/form-nodes';
import type { FieldNode, FormValue, ValidationError } from '@ngblocks/form-nodes';

_FormNode is framework infrastructure exported for Angular's compiler and linker. Applications must use FormNodeDirective instead.

For behavioral details that are intentionally too specialized for the normal reference flow, see Advanced behavior and edge cases.

Submission context​

useClosestFormState() exposes shared submitted() state from Form Nodes, Reactive Forms, or NgForm. Its reactive formNode() property provides the owning Form Nodes API when available, including submitting() for an action currently in progress.

See Public types for every exported type alias and interface. For integration workflows, start with custom control contracts or validation error types.