https://github.com/ngx-vest-forms/ngx-vest-forms
Simple form development for complex and scalable solutions
https://github.com/ngx-vest-forms/ngx-vest-forms
angular template-driven-forms validation vestjs
Last synced: 2 months ago
JSON representation
Simple form development for complex and scalable solutions
- Host: GitHub
- URL: https://github.com/ngx-vest-forms/ngx-vest-forms
- Owner: ngx-vest-forms
- Created: 2024-06-06T14:54:23.000Z (almost 2 years ago)
- Default Branch: master
- Last Pushed: 2026-03-24T11:05:35.000Z (2 months ago)
- Last Synced: 2026-03-25T13:22:16.137Z (2 months ago)
- Topics: angular, template-driven-forms, validation, vestjs
- Language: TypeScript
- Homepage:
- Size: 19.9 MB
- Stars: 57
- Watchers: 3
- Forks: 5
- Open Issues: 8
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- trackawesomelist - ngx-vest-forms (β43) - A lightweight adapter for Angular template-driven forms integrated with vest.js for validation. (Recently Updated / [Sep 02, 2024](/content/2024/09/02/README.md))
- awesome-angular - ngx-vest-forms - A lightweight, type-safe adapter linking Angular template-driven forms with [Vest.js](https://vestjs.dev/) for complex, async validation. (Third Party Components / Forms)
- fucking-awesome-angular - ngx-vest-forms - A lightweight, type-safe adapter linking Angular template-driven forms with π [Vest.js](vestjs.dev/) for complex, async validation. (Third Party Components / Forms)
README
# ngx-vest-forms
A lightweight, type-safe adapter between Angular template-driven forms and [Vest.js](https://vestjs.dev) validation. Build complex forms with unidirectional data flow, sophisticated async validations, and minimal boilerplate.
[](https://www.npmjs.com/package/ngx-vest-forms)
[](https://github.com/ngx-vest-forms/ngx-vest-forms/actions/workflows/cd.yml)
[![Angular]()](https://angular.dev)
[](https://www.typescriptlang.org)
[](LICENSE)
β If you like this project, star it on GitHub β it helps a lot!
[Quick Start](#installation--quick-start) β’ [Docs](#documentation) β’ [Key Features](#key-features) β’ [Migration](#migration) β’ [FAQ](#faq) β’ [Resources](#resources)
> **New Maintainer**:
>
> I'm [the-ult](https://bsky.app/profile/the-ult.bsky.social), now maintaining this project as Brecht Billiet has moved on to other priorities. Huge thanks to Brecht for creating this amazing library and his foundational work on Angular forms!
## Why ngx-vest-forms?
- Unidirectional state with Angular signals
- Type-safe template-driven forms with runtime shape validation (dev only)
- Powerful Vest.js validations (sync/async, conditional, composable)
- Minimal boilerplate: controls and validation wiring are automatic
See the full guides under [Documentation](#documentation).
## Installation & Quick Start
### Prerequisites
- **Angular**: >=19.0.0 minimum, 20.x recommended (all used APIs stable)
- **Vest.js**: >=5.4.6 (Validation engine)
- **TypeScript**: >=5.8.0 (Modern Angular features)
- **Node.js**: >=20 (Maintenance release)
### Installation
```bash
npm install ngx-vest-forms
```
> **v.2.0.0 NOTE:**
>
> You must call `only()` **unconditionally** in Vest suites.
>
> ```ts
> // β
Correct
> only(field); // only(undefined) safely runs all tests
> ```
>
> Why: Conditional `only()` breaks Vest's change detection mechanism and causes timing issues with `omitWhen` + `validationConfig` in ngx-vest-forms.
> See the [Migration Guide](./docs/migration/MIGRATION-v1.x-to-v2.0.0.md#1-unconditional-only-pattern-required-critical).
>
> Selector prefix: use `ngx-` (recommended). The legacy `sc-` works in v2.x but is deprecated and will be removed in v3.
### Quick Start
Start simple (with validations):
```ts
import { Component, signal } from '@angular/core';
import { NgxVestForms, NgxDeepPartial, NgxVestSuite } from 'ngx-vest-forms';
import { staticSuite, only, test, enforce } from 'vest';
type MyFormModel = NgxDeepPartial<{ email: string; name: string }>;
// Minimal validation suite (always call only(field) unconditionally)
const suite: NgxVestSuite = staticSuite((model, field?) => {
only(field);
test('email', 'Email is required', () => {
enforce(model.email).isNotBlank();
});
});
@Component({
imports: [NgxVestForms],
template: `
Email
Name
Submit
`,
})
export class MyComponent {
protected readonly formValue = signal({});
protected readonly suite = suite;
}
```
Notes.
- Use `[ngModel]` (not `[(ngModel)]`) for unidirectional data flow
- The `?` operator is required because template-driven forms build values incrementally (`NgxDeepPartial`)
- The `name` attribute MUST exactly match the property path used in `[ngModel]` β see [Field Paths](./docs/FIELD-PATHS.md)
That's all you need. The directive automatically creates controls, wires validation, and manages state.
## Key Features
- **Unidirectional state with signals** β Models are `NgxDeepPartial` so values build up incrementally
- **Type-safe with runtime shape validation** β Automatic control creation and validation wiring (dev mode checks)
- **Vest.js validations** β Sync/async, conditional, composable patterns with `only(field)` optimization
- **Error display modes** β Control when errors show: `on-blur`, `on-submit`, `on-blur-or-submit` (default), `on-dirty`, or `always`
- **Warning display modes** β Control when warnings show: `on-touch`, `on-validated-or-touch` (default), `on-dirty`, or `always`
- **Form state tracking** β Access touched, dirty, valid/invalid states for individual fields or entire form
- **Error display helpers** β `ngx-control-wrapper` component (recommended) plus directive building blocks for custom wrappers:
- `ngx-form-group-wrapper` component (recommended for `ngModelGroup` containers)
- `FormErrorDisplayDirective` (state + display policy)
- `FormErrorControlDirective` (adds ARIA wiring + stable region IDs)
- **Cross-field dependencies** β `validationConfig` for field-to-field triggers, `ROOT_FORM` for form-level rules
- **Utilities** β Field paths, field clearing, validation config builder
### Compatibility & Safety Notes (v2.x)
- `ROOT_FORM_CONSTANT` is retained for compatibility but deprecated; prefer `ROOT_FORM`.
- `set` / `cloneDeep` are retained for compatibility; prefer `setValueAtPath` / `structuredClone` in new code.
### Error & Warning Display Modes
Control when validation errors and warnings are shown to users with multiple built-in modes:
#### Error Display Modes
```typescript
// Global configuration via DI token
import { NGX_ERROR_DISPLAY_MODE_TOKEN } from 'ngx-vest-forms';
providers: [
{ provide: NGX_ERROR_DISPLAY_MODE_TOKEN, useValue: 'on-dirty' }
]
// Recommended: Use ngx-control-wrapper component
```
| Mode | Behavior |
| --------------------- | ---------------------------------------------------- |
| `'on-blur-or-submit'` | Show after blur OR form submit (default) |
| `'on-blur'` | Show only after blur/touch |
| `'on-submit'` | Show only after form submission |
| `'on-dirty'` | Show as soon as value changes (or after blur/submit) |
| `'always'` | Show immediately, even on pristine fields |
#### Warning Display Modes
```typescript
// Global configuration via DI token
import { NGX_WARNING_DISPLAY_MODE_TOKEN } from 'ngx-vest-forms';
providers: [
{ provide: NGX_WARNING_DISPLAY_MODE_TOKEN, useValue: 'always' }
]
// Per-instance configuration
```
| Mode | Behavior |
| ------------------------- | ---------------------------------------------------- |
| `'on-validated-or-touch'` | Show after validation runs or touch (default) |
| `'on-touch'` | Show only after blur/touch |
| `'on-dirty'` | Show as soon as value changes (or after blur/submit) |
| `'always'` | Show immediately, even on pristine fields |
#### Group-Safe Mode Example
```html
// Group-safe mode (use this on an ngModelGroup container)
Street
City
```
#### ARIA association (advanced)
`` can optionally apply `aria-describedby` / `aria-invalid` to **descendant** controls.
This is controlled by `ariaAssociationMode`:
- `"all-controls"` (default) β stamps all descendant `input/select/textarea`
- `"single-control"` β stamps only if exactly one control exists (useful for input + extra buttons)
- `"none"` β never mutates descendant controls (group-safe / manual wiring)
For `ngModelGroup` containers, prefer using `` (group-safe by default).
π See also:
- [Accessibility Guide](./docs/ACCESSIBILITY.md)
- [`ControlWrapperComponent` docs](./projects/ngx-vest-forms/src/lib/components/control-wrapper/README.md)
> **Styling note**: `ngx-control-wrapper` uses Tailwind CSS utility classes for default styling.
> If your project doesn't use Tailwind, see the [component docs](./projects/ngx-vest-forms/src/lib/components/control-wrapper/README.md#styling-dependency-tailwind-css) for alternatives.
π **[Complete Guide: Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)**
### Form State
Access complete form and field state through the `FormErrorDisplayDirective` or `FormControlStateDirective`:
```typescript
@Component({
template: `
@if (wrapper.isTouched()) {
Field was touched
}
@if (wrapper.isPending()) {
Validating...
}
`
})
```
**Available state signals:**
- `isTouched()` / `isDirty()` β User interaction state
- `isValid()` / `isInvalid()` β Validation state
- `isPending()` β Async validation in progress
- `errorMessages()` / `warningMessages()` β Current validation messages
- `shouldShowErrors()` / `shouldShowWarnings()` β Computed based on display mode and state
**Warnings behavior:**
- Warnings are **non-blocking** and do not make a field invalid.
- They are stored separately from `control.errors` and are cleared on `resetForm()`.
- These messages may appear after `validationConfig` triggers validation, even if the field was not touched yet.
- Use `NGX_WARNING_DISPLAY_MODE_TOKEN` to control when warnings display (see [Warning Display Modes](#warning-display-modes)).
**Tip**: For async validations, use `createDebouncedPendingState()` to prevent "Validating..." messages from flashing when validation completes quickly (< 200ms).
π **[Complete Guide: Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)**
## Advanced Features
### Validation Config
Automatically re-validate dependent fields when another field changes. Essential when using Vest.js's `omitWhen`/`skipWhen` for conditional validations.
**When to use**: Password confirmation, conditional required fields, or any field that depends on another field's value.
```typescript
protected readonly validationConfig = {
'password': ['confirmPassword'], // When password changes, re-validate confirmPassword
'age': ['emergencyContact'] // When age changes, re-validate emergencyContact
};
```
**Important**: `validationConfig` only triggers re-validationβvalidation logic is always defined in your Vest suite.
π **[Complete Guide: ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)**
### Root-Form Validation
Form-level validation rules that don't belong to any specific field (e.g., "at least one contact method required").
**When to use**: Business rules that evaluate multiple fields but errors should appear at form level, not on individual fields.
```typescript
import { ROOT_FORM } from 'ngx-vest-forms';
// In your Vest suite
test(ROOT_FORM, 'At least one contact method is required', () => {
enforce(model.email || model.phone).isTruthy();
});
```
```html
{{ vestForm.errors.rootForm }}
```
π **[Complete Guide: ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)**
### Dynamic Form Structure
Manually trigger validation when form structure changes between **input fields and non-input content** (like `
` tags) without value changes.
**When to use**: When switching from form controls to informational text/paragraphs where no control values change.
**NOT needed when**: Switching between different input fields (value changes trigger validation automatically).
**IMPORTANT**: `triggerFormValidation()` only re-runs validation logicβit does NOT mark fields as touched or show errors.
> **Note on form submission**: With the default `on-blur-or-submit` error display mode, errors are shown automatically when you submit via `(ngSubmit)`. The form automatically calls `markAllAsTouched()` internally. You only need to call `markAllAsTouched()` manually for special cases like multiple forms with one submit button.
```typescript
// Structure change: Re-run validation
@if (type() === 'typeA') {
} @else {
No input required
// β No form control, needs triggerFormValidation()
}
onTypeChange(newType: string) {
this.formValue.update(v => ({ ...v, type: newType }));
this.vestForm.triggerFormValidation(); // Re-runs validation, doesn't show errors
}
// Standard form submission - NO manual call needed!
// Errors shown automatically via (ngSubmit) with default on-blur-or-submit mode
Submit
// Multiple forms with one button - NEED manual markAllAsTouched()
submitBoth() {
this.form1().markAllAsTouched();
this.form2().markAllAsTouched();
if (this.form1().valid && this.form2().valid) {
// Submit logic
}
}
```
π **[Complete Guide: Structure Change Detection](./docs/STRUCTURE_CHANGE_DETECTION.md)**
### Shape Validation (Development Mode)
In development mode, ngx-vest-forms validates that your form's structure matches your TypeScript model, catching common mistakes early:
```typescript
// Your model
type MyFormModel = NgxDeepPartial<{
email: string;
address: { street: string; city: string };
}>;
// Define shape for runtime validation
const shape: NgxDeepRequired = {
email: '',
address: { street: '', city: '' },
};
```
```html
```
**Benefits:**
- Catch typos in `name` attributes immediately during development
- Ensure template structure matches TypeScript model
- Zero runtime cost in production (checks disabled automatically)
- Works with nested objects and arrays
**Important**: Shape validation only runs in development mode (`isDevMode()` returns `true`). Production builds have zero overhead.
π **[Complete Guide: Field Paths](./docs/FIELD-PATHS.md)**
## Documentation
### Getting Started
- **[Complete Example](./docs/COMPLETE-EXAMPLE.md)** - Step-by-step walkthrough from basic form to advanced patterns
- **[Composable Validations](./docs/COMPOSABLE-VALIDATIONS.md)** - Break validation logic into reusable, testable functions
### Advanced Patterns
- **[ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)** - Cross-field dependencies and form-level rules
- **[Clear Submitted State](./docs/CLEAR-SUBMITTED-STATE.md)** - End a submit cycle without resetting values or control metadata
- **[Field Path Types](./docs/FIELD-PATHS.md)** - Type-safe dot-notation paths for nested properties
- **[Structure Change Detection](./docs/STRUCTURE_CHANGE_DETECTION.md)** - Handle dynamic form structure updates
- **[Field Clearing Utilities](./docs/FIELD-CLEARING-UTILITIES.md)** - Type-safe utilities for clearing nested form values
### UI & Integration
- **[Child Components](./docs/CHILD-COMPONENTS.md)** - Split large forms into smaller, maintainable components
- **[Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)** - Build consistent error display patterns
- **[API Tokens](./docs/API-TOKENS.md)** - Configure error display modes and other global settings
### Reference
- **[Utilities README](./projects/ngx-vest-forms/src/lib/utils/README.md)** - Canonical reference for all utility functions
### Examples
- **[Examples Project](./projects/examples)** - Working code examples with business hours forms, purchase forms, and validation config demos
- Run locally: `npm install && npm start`
- Includes smart components, UI components, and complete validation patterns
## Migration
- v1.x β v2.0.0: **[Migration Guide](./docs/migration/MIGRATION-v1.x-to-v2.0.0.md)**
- Selector prefixes: **[Dual Selector Support](./docs/DUAL-SELECTOR-SUPPORT.md)**
Browser support follows Angular 19+ targets (no `structuredClone` polyfill required).
## FAQ
### Do I need validations to use ngx-vest-forms?
Noβbut youβll almost always want them. Common cases to start without a suite:
- Prototyping UI while deferring rules
- Gradual migration: adopt unidirectional state and type-safe models first
- Server-driven validation: display backend errors while you add a client suite later
You can add a Vest suite at any time by binding `[suite]` on the form.
## Resources
### Documentation & Tutorials
- **[Angular Official Documentation](https://angular.dev/guide/forms)** - Template-driven forms guide
- **[Vest.js Documentation](https://vestjs.dev)** - Validation framework used by ngx-vest-forms
- **[Live Examples Repository](https://github.com/ngx-vest-forms/ngx-vest-forms/tree/master/projects/examples)** - Complex form examples and patterns
### Running Examples Locally
```bash
npm install
npm start
```
### Learning Resources
**[Complex Angular Template-Driven Forms Course](https://www.simplified.courses/complex-angular-template-driven-forms)** - Master advanced form patterns and become a form expert.
### Founding Articles by Brecht Billiet
This library was originally created by [Brecht Billiet](https://twitter.com/brechtbilliet). Here are his foundational blog posts that inspired and guided the development:
- **[Introducing ngx-vest-forms](https://blog.simplified.courses/introducing-ngx-vest-forms/)** - The original introduction and motivation
- **[Making Angular Template-Driven Forms Type-Safe](https://blog.simplified.courses/making-angular-template-driven-forms-typesafe/)** - Deep dive into type safety
- **[Asynchronous Form Validators in Angular with Vest](https://blog.simplified.courses/asynchronous-form-validators-in-angular-with-vest/)** - Advanced async validation patterns
- **[Template-Driven Forms with Form Arrays](https://blog.simplified.courses/template-driven-forms-with-form-arrays/)** - Dynamic form arrays implementation
## Developer Resources
### Agent Skills
This repository ships an installable agent skill for ngx-vest-forms guidance.
Install it from the repository root with the `skills` CLI:
```bash
npx skills add ngx-vest-forms/ngx-vest-forms --skill ngx-vest-forms
```
This repository also ships an installable agent skill for **Vest.js 5.4 guidance**.
Install it from the repository root with the `skills` CLI:
```bash
npx skills add ngx-vest-forms/ngx-vest-forms --skill vestjs
```
See also: **[Vest.js 5.4 Agent Skill Guide](./docs/VESTJS-SKILL.md)**
### Comprehensive Instruction Files
This project includes detailed instruction files designed to help developers master ngx-vest-forms and Vest.js patterns:
- **[`.github/instructions/ngx-vest-forms.instructions.md`](.github/instructions/ngx-vest-forms.instructions.md)** - Complete guide for using ngx-vest-forms library
- **[`.github/instructions/vest.instructions.md`](.github/instructions/vest.instructions.md)** - Comprehensive Vest.js validation patterns and best practices
- **[`.github/copilot-instructions.md`](.github/copilot-instructions.md)** - Main GitHub Copilot instructions for this workspace
## Acknowledgments
π **Special thanks to [Brecht Billiet](https://twitter.com/brechtbilliet)** for creating the original version of this library and his pioneering work on Angular forms. His vision and expertise laid the foundation for what ngx-vest-forms has become today.
### Core Contributors & Inspirations
**[Evyatar Alush](https://twitter.com/evyataral)** - Creator of [Vest.js](https://vestjs.dev/)
- π― **The validation engine** that powers ngx-vest-forms
- ποΈ **Featured on PodRocket**: [Vest with Evyatar Alush](https://dev.to/podrocket/vest-with-evyatar-alush) - Deep dive into the philosophy and architecture of Vest.js
**[Ward Bell](https://twitter.com/wardbell)** - Template-Driven Forms Advocate
- π’ **Evangelized Template-Driven Forms**: [Prefer Template-Driven Forms](https://devconf.net/talk/prefer-template-driven-forms-ward-bell-ng-conf-2021) (ng-conf 2021)
- π₯ **Original Vest.js + Angular Integration**: [Form validation done right](https://www.youtube.com/watch?v=EMUAtQlh9Ko) - The foundational talk that inspired this approach
- π» **Early Implementation**: [ngc-validate](https://github.com/wardbell/ngc-validate) - The initial version of template-driven forms with Vest.js
These pioneers laid the groundwork that made ngx-vest-forms possible, combining the power of declarative validation with the elegance of Angular's template-driven approach.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.