Skip to main content

configureGlobalFormNodes()

Configures process-wide defaults for validator messages and automatic classes. Optional experimental control integration is documented at the end of this page. Angular providers override each option independently. The exported GlobalFormNodesConfig type describes these options.

๐Ÿ“ Signatureโ€‹

configureGlobalFormNodes(config: {
validatorMessages?: ValidatorMessages | (() => ValidatorMessages | undefined) | null | undefined;
classes?: Record<string, (binding: FormNodeBinding) => boolean> | null | undefined;
bindInputOutputPairs?: boolean | null | undefined; // Experimental; default: false
syncInputs?: false | 'declared' | 'all' | 'signal-controls' | readonly SyncInputName[]
| { inputs: 'declared' | 'all' | readonly SyncInputName[]; target?: 'all' | 'signal-controls' | 'cva' } | null | undefined; // Experimental
}): () => void;

๐Ÿ’ก Where to call itโ€‹

Call this function in main.ts, before bootstrapApplication(), or before bootstrapping AppModule. Keep startup configuration active for the application's lifetime. It requires no injection context or Angular initializer.

A larger catalog can live in its own file, exporting data without configuring global state:

validator-message-catalog.ts
import type { ValidatorMessages } from '@ngblocks/form-nodes';

export const validatorMessages: ValidatorMessages = {
required: 'This value is required.',
email: 'Enter a valid email address.',
min: ({ min }) => `Enter a value of at least ${min}.`,
};

This complete example configures the library and bootstraps a small application. Comments mark the suggested files for each section; imports stay together so the combined example is executable.

Application startup
// Imports shared by the combined example.
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { field, form, required, FormNodeDirective, ANGULAR_FORMS_STATUS_CLASSES, configureGlobalFormNodes } from '@ngblocks/form-nodes';
import { validatorMessages } from './validator-message-catalog';

// app.component.ts
@Component({
selector: 'app-root',
template: `
<label>Name <input [formNode]="form.name"></label>
@if (form.name.touched()) {
<p>{{ form.name.getError('required')?.message }}</p>
}
`,
imports: [FormNodeDirective],
})
class AppComponent {
form = form({ name: field('', [required]) });
}

// main.ts
configureGlobalFormNodes({
validatorMessages,
classes: ANGULAR_FORMS_STATUS_CLASSES,
syncInputs: 'declared',
});

bootstrapApplication(AppComponent).catch(error => console.error(error));

Use provideFormNodesConfig() for application-, route-, module-, or component-scoped overrides. Global state is shared across Angular applications and SSR requests in the same JavaScript module instance; request-specific configuration belongs in providers or form options.

โš™๏ธ Independent options and precedenceโ€‹

For each binding option, resolution is: nearest explicit Angular provider โ†’ global setting โ†’ library default. For messages, validator and form-tree overrides retain higher precedence; global messages remain the fallback after catalogs captured by nodes from Angular providers.

OptionGlobal behaviornull resets to
validatorMessagesStatic or reactive fallback catalogEmpty catalog, leaving built-in messages as the final fallback
classesClass map captured by new bindingsNo automatic classes

Omitting an option or passing undefined preserves the current global setting. Multiple calls update only the supplied options. Explicit catalogs and class maps replace their previous maps; they do not merge entries automatically.

A provider with classes: null explicitly selects the library default, bypassing the global value for that option. A provider with validatorMessages: null supplies an empty provider catalog; normal message fallback still includes the global catalog. It does not force built-in English text.

๐Ÿ’ฌ Reactive messages and binding snapshotsโ€‹

Global catalog functions run reactively during failing validation and may return undefined to fall back to built-in messages. They are reactive sources, not injectable factories: this API does not create an injection context. Use the provider's factory when a catalog needs inject(). Selected message callbacks can also read signals. Replacing or restoring global messages updates existing failing nodes, including nodes declared outside Angular DI.

Class predicates remain reactive after a binding captures their map. Changing the global class map later affects new bindings; it does not reconfigure existing ones. Configure those defaults before bootstrap. Native-control state, value/checked binding, touch, focus, and reset behavior retain their existing rules.

โš™๏ธ Restoring temporary configurationโ€‹

The returned callback removes only the overrides installed by its call. It is idempotent and preserves later overrides of the same option. Cleanup can run out of order: when a later override is removed, previously cleaned-up overrides are skipped instead of being reactivated. Restoring binding defaults affects future bindings; restoring messages also updates existing nodes.

This executable example checks partial updates, reactive messages, resets, and restoration:

global-form-nodes.example.ts
import { signal } from '@angular/core';
import { field, form, required, configureGlobalFormNodes } from '@ngblocks/form-nodes';

const message = signal('Please enter your name.');
const profile = form({ name: field('', [required]) });
const restore = configureGlobalFormNodes({
validatorMessages: { required: () => message() },
});
const restoreBindings = configureGlobalFormNodes({ syncInputs: false });

try {
if (profile.name.getError('required')?.message !== 'Please enter your name.') {
throw new Error('A partial binding configuration must preserve global messages.');
}
message.set('A name is required.');
if (profile.name.getError('required')?.message !== 'A name is required.') {
throw new Error('Global message callbacks must remain reactive.');
}
const restoreReset = configureGlobalFormNodes({ validatorMessages: null });
try {
if (profile.name.getError('required')?.message !== 'This field is required.') {
throw new Error('A global message reset must allow the built-in fallback.');
}
restore();
} finally {
restoreReset();
}
if (profile.name.getError('required')?.message !== 'This field is required.') {
throw new Error('Restoring a later override must skip an already cleaned-up catalog.');
}
} finally {
restoreBindings();
restore();
}

See Configuration and Validator messages.

๐Ÿงช syncInputs (experimental)โ€‹

Disabled by default. This optional setting writes node state and constraints into matching component inputs through Angular internals. Standard value models and CVAs work without it.

Use syncInputs: 'signal-controls' for all supported state inputs on actual model controls, or { inputs: ['disabled'], target: 'cva' } for selected CVA inputs. The selected adapter determines the target even when a component offers both contracts. This option never enables paired value binding.

See the complete input selections and behavior.

๐Ÿงช bindInputOutputPairs (experimental)โ€‹

Disabled by default. Set bindInputOutputPairs: true to connect separate value/valueChange or checked/checkedChange pairs. These value writes use Angular internals. Value models and standard CVAs remain connected independently of this setting.

See paired binding and its lifecycle.

โ—† Scope of experimental defaultsโ€‹

Each option resolves independently: explicit node option โ†’ nearest explicit Angular provider โ†’ global setting โ†’ library default (false). False or null disables that option without changing the other. A provider with null explicitly disables it even when the global setting enables it.

Omission or undefined preserves the current global setting. Changing or restoring these global options affects future bindings or control connections; it does not reconfigure existing ones. Configure them before bootstrap when opting in.