<!-- prettier-ignore -->
<div align="center">

# 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.

[![npm version](https://img.shields.io/npm/v/ngx-vest-forms.svg?style=flat-square)](https://www.npmjs.com/package/ngx-vest-forms)
[![Build Status](https://img.shields.io/github/actions/workflow/status/ngx-vest-forms/ngx-vest-forms/cd.yml?branch=master&style=flat-square&label=Build)](https://github.com/ngx-vest-forms/ngx-vest-forms/actions/workflows/cd.yml)
[![Angular](<https://img.shields.io/badge/Angular-19+%20(min)%20%E2%80%94%2020%20recommended-dd0031?style=flat-square&logo=angular>)](https://angular.dev)
[![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
[![License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](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)

</div>

> **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<MyFormModel> = staticSuite((model, field?) => {
  only(field);
  test('email', 'Email is required', () => {
    enforce(model.email).isNotBlank();
  });
});

@Component({
  imports: [NgxVestForms],
  template: `
    <form ngxVestForm [suite]="suite" (formValueChange)="formValue.set($event)">
      <ngx-control-wrapper>
        <label for="email">Email</label>
        <input id="email" name="email" [ngModel]="formValue().email" />
        <!-- Errors display automatically below input -->
      </ngx-control-wrapper>

      <ngx-control-wrapper>
        <label for="name">Name</label>
        <input id="name" name="name" [ngModel]="formValue().name" />
      </ngx-control-wrapper>

      <button type="submit">Submit</button>
    </form>
  `,
})
export class MyComponent {
  protected readonly formValue = signal<MyFormModel>({});
  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<T>` 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
- **Field blur events** — `fieldBlur` output for blur-driven draft auto-save, analytics, and field-level side effects
- **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
<ngx-control-wrapper [errorDisplayMode]="'on-blur'">
  <input name="email" [ngModel]="formValue().email" />
</ngx-control-wrapper>
```

| 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
<ngx-control-wrapper [warningDisplayMode]="'on-dirty'">
  <input name="username" [ngModel]="formValue().username" />
</ngx-control-wrapper>
```

| 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)
<ngx-form-group-wrapper ngModelGroup="address">
  <ngx-control-wrapper>
    <label for="street">Street</label>
    <input id="street" name="street" [ngModel]="formValue().address?.street" />
  </ngx-control-wrapper>

  <ngx-control-wrapper>
    <label for="city">City</label>
    <input id="city" name="city" [ngModel]="formValue().address?.city" />
  </ngx-control-wrapper>
</ngx-form-group-wrapper>
```

#### ARIA association (advanced)

`<ngx-control-wrapper>` 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 `<ngx-form-group-wrapper>` (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: `
    <ngx-control-wrapper #wrapper="ngxErrorDisplay">
      <input name="email" [ngModel]="formValue().email" />

      @if (wrapper.isTouched()) {
        <span>Field was touched</span>
      }
      @if (wrapper.isPending()) {
        <span>Validating...</span>
      }
    </ngx-control-wrapper>
  `
})
```

**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.

For dependent fields that should become invalid immediately but stay visually quiet until the
target field's own blur/display policy allows errors, combine `validationConfig` with
`errorDisplayMode="on-blur"` on the target wrappers:

```typescript
protected readonly validationConfig = createValidationConfig<FormModel>()
  .bidirectional('quantity', 'justification')
  .build();
```

This pairs well with `<ngx-control-wrapper [errorDisplayMode]="'on-blur'">`.

Avoid calling `triggerFormValidation()` from field-level blur handlers to force this UX.
`validationConfig` already re-runs the dependent validation, and the wrapper's
`errorDisplayMode` decides when the dependent field becomes visibly noisy.
Extra blur-triggered validation can restart async validators unnecessarily and make
the flow harder to reason about.

If you also want draft auto-save on blur, keep persistence separate from validation and
listen to the form's `fieldBlur` output. That gives you immediate dependent validity,
quiet untouched dependents, and blur-triggered save orchestration without coupling the
library to persistence policy.

📖 **[Complete Guide: ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)**

### Field Blur Events & Draft Auto-Save

Use the form's `fieldBlur` output to build blur-driven workflows such as draft auto-save.

```typescript
protected handleFieldBlur(event: NgxFieldBlurEvent<FormModel>): void {
  if (!event.formValue || !event.dirty) {
    return;
  }

  this.saveDraft(event.formValue);
}
```

```html
<form
  ngxVestForm
  [formValue]="formValue()"
  (formValueChange)="formValue.set($event)"
  (fieldBlur)="handleFieldBlur($event)"
>
  <ngx-control-wrapper>
    <label for="projectName">Project name</label>
    <input id="projectName" name="projectName" [ngModel]="formValue().projectName" />
  </ngx-control-wrapper>
</form>
```

The output payload includes:

- `field`
- `value`
- `formValue`
- `dirty`
- `touched`
- `valid`
- `pending`

For draft persistence, prefer treating `pending` as informational metadata rather
than a blocker. If you gate blur saves on `pending`, async validation can prevent
the latest draft from being persisted even though the user has finished editing.

Both blur-save policies are supported:

- **Always save drafts** — recommended for draft persistence and recovery-oriented UX
- **Only save if valid** — useful when blur triggers stricter side effects instead of draft saves

For the valid-only variant, layer app policy on top of `fieldBlur`, for example:

```typescript
protected handleFieldBlur(event: NgxFieldBlurEvent<FormModel>): void {
  if (!event.formValue || !event.dirty || !event.valid || event.pending) {
    return;
  }

  this.saveDraft(event.formValue);
}
```

The examples app includes a complete blur-driven draft persistence implementation:

- route: `/auto-save-demo`
- source: `projects/examples/src/app/pages/auto-save-demo/`

That example intentionally demonstrates the **always-save draft** policy.

📖 **[Guide: Auto-Save on Blur](./docs/AUTO-SAVE-ON-BLUR.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
<!-- In template -->
<form ngxVestForm ngxValidateRootForm [suite]="suite">
  <!-- Show form-level errors -->
  <div *ngIf="vestForm.errors?.rootForm">{{ vestForm.errors.rootForm }}</div>
</form>
```

📖 **[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 `<p>` 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') {
  <input name="fieldA" [ngModel]="formValue().fieldA" />
} @else {
  <p>No input required</p>  // ← 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
<form ngxVestForm (ngSubmit)="save()">
  <!-- ... -->
  <button type="submit">Submit</button>
</form>

// 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<MyFormModel> = {
  email: '',
  address: { street: '', city: '' },
};
```

```html
<form ngxVestForm [suite]="suite" [formShape]="shape">
  <!-- ✅ Correct: matches shape -->
  <input name="email" [ngModel]="formValue().email" />
  <input name="address.street" [ngModel]="formValue().address?.street" />

  <!-- ❌ Error in dev mode: typo detected -->
  <input name="emial" [ngModel]="formValue().email" />

  <!-- ❌ Error in dev mode: path doesn't exist in shape -->
  <input name="address.zipcode" [ngModel]="formValue().address?.zipcode" />
</form>
```

**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
- **[Auto-Save on Blur](./docs/AUTO-SAVE-ON-BLUR.md)** - Build draft persistence with `fieldBlur` and calm dependent validation
- **[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
- **[Composite Adapter Recipe](./docs/COMPOSITE-ADAPTER-RECIPE.md)** - Map one composite widget to multiple real form field paths
- **[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, validation config demos, and blur-driven draft auto-save
  - 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.
