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 with | Details |
|---|---|---|
| Model one logical value | field() | field() reference |
| Type a node input or reusable utility | AnyNode, DynamicNode, or a concrete node type | Node types reference |
| Organize unrelated standalone nodes without aggregate behavior | Plain JavaScript object | Creating nodes |
| Define a submission workflow boundary | form() | form() reference |
| Choose the default field nullability for an application | createFormPrimitives() | createFormPrimitives() reference |
| Check an inferred aggregate against a named value model | FormValueContract<TValue> | FormValueContract reference |
| Extract the value type of any node | FormNodeValue<typeof node> | FormNodeValue reference |
| Model a dynamic ordered collection of independent nodes | array() | array() reference |
| Give an object branch its own options without creating a submission workflow | Explicit group() | group() reference |
| Choose between a structured field, form, or array | β | Choosing a primitive |
| Design a large domain-oriented form tree | Modeling boundaries and lifecycle | Form modeling patterns |
| Add a built-in validation rule | required, email, min, and others | Built-in validators |
| Understand validation across every node | validators, errors, and status | Validation reference |
| Author a reusable synchronous rule | validator() | validator() reference |
| Run Promise- or Observable-based validation | asyncValidator() | asyncValidator() reference |
| Bind a node to an Angular control | FormNodeDirective and [formNode] | FormNodeDirective binding API |
Submit through a native <form> | FormNodeDirective | Form submission |
| Configure validator messages through Angular DI | provideFormNodesConfig() | provideFormNodesConfig() |
| Configure process-wide messages and binding defaults | configureGlobalFormNodes() | configureGlobalFormNodes() |
| Add reactive status classes to every binding | provideFormNodesConfig() | provideFormNodesConfig() |
| Inspect the API shared by all nodes | AnyNode, DynamicNode, and NodeApi | Node API |
| Test a form model or Angular binding | Public node API and, when needed, TestBed | Testing forms |
| Use Angular Material controls | FormNodeDirective with Material's normal modules | Angular Material integration |
| Use PrimeNG controls | FormNodeDirective with PrimeNG's normal modules | PrimeNG 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:
- Validator-local
message. - Closest form or array
validatorMessagescatalog. - Closest
createFormPrimitives()validator-message default. - Closest
provideFormNodesConfig()provider. configureGlobalFormNodes().- 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β
| API | Purpose |
|---|---|
provideFormNodesConfig() | Configures validator messages, custom-control inputs, and reactive CSS classes. |
ANGULAR_FORMS_STATUS_CLASSES | Optional 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. |
FormNodeCheckboxControl | Boolean 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(). |
FormNodeErrors | Error messages with touch-or-submit visibility, custom templates, and optional height animation. |
FormNodeErrorsContext | First message, visible messages, and error details for a nested #message template. |
ControlStateDisabledReason | Source-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:
| Area | Main members |
|---|---|
| Value | Node call, value(), value.control(), set(), update(), reset() |
| Validation | errors(), allErrors(), getError(), valid(), invalid(), pending() |
| Interaction | touched(), dirty(), their complements, and marking methods |
| Availability | disabled(), readonly(), hidden(), their complements, reasons, and actions |
| Tree | form(), root(), parent(), path(), keyInParent(), and object-node add(), get(), remove() |
| Controls | debouncing(), 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.