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.
🔗 Related guides and reference​
- 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.