Skip to main content

3. Add validation

Add built-in validators where each field is declared:

form = form({
name: field('', [required, minLength(2)]),
age: field<number>(null, [between(18, 120), integer]),
email: field('', [required, email]),
});

Import the rules with the form primitives:

import { FormNodeDirective, between, email, field, form, integer, minLength, required } from '@ngblocks/form-nodes';

Optional format and constraint validators accept empty values so they compose with required. For example, email checks format while required checks presence.

🚨 Render errors after interaction​

Use getError() for a specific rule. Known built-in kinds expose their typed payload:

<label>
Name
<input [formNode]="form.name" />
</label>

@if (form.name.touched()) {
@if (form.name.getError('required'); as error) {
<p class="error">{{ error.message }}</p>
}
@if (form.name.getError('minLength'); as error) {
<p class="error">
Enter at least {{ error.minLength }} characters.
</p>
}
}

Use errors() for errors owned by one node and allErrors() for a complete subtree:

this.form.name.errors();
this.form.errors();
this.form.allErrors();

A field error makes every interactive ancestor invalid. The error remains owned by its field through targetNode.

  • Validation covers custom validators, cross-field rules, reactive constraints, and validator metadata.
  • Errors and status explains errors(), allErrors(), getError(), ownership, targetNode, and aggregate validity.
  • Built-in validators documents every signature, error shape, empty-value rule, and concrete example.
  • Validator messages and i18n covers default messages and global, provider, form-tree, and validator-local overrides.

Continue with Step 4: Add nesting and reactive state.