npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ngx-vest-forms

v2.6.0

Published

Opinionated template-driven forms library for Angular with Vest.js integration

Readme

ngx-vest-forms

A lightweight, type-safe adapter between Angular template-driven forms and Vest.js validation. Build complex forms with unidirectional data flow, sophisticated async validations, and minimal boilerplate.

npm version Build Status Angular TypeScript License

⭐ If you like this project, star it on GitHub — it helps a lot!

Quick StartDocsKey FeaturesMigrationFAQResources

New Maintainer:

I'm the-ult, 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.

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

npm install ngx-vest-forms

v.2.0.0 NOTE:

You must call only() unconditionally in Vest suites.

// ✅ 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.

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):

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

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 helpersngx-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 dependenciesvalidationConfig 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

// 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

// 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

// 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:

Styling note: ngx-control-wrapper uses Tailwind CSS utility classes for default styling. If your project doesn't use Tailwind, see the component docs for alternatives.

📖 Complete Guide: Custom Control Wrappers

Form State

Access complete form and field state through the FormErrorDisplayDirective or FormControlStateDirective:

@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).

Tip: For async validations, use createDebouncedPendingState() to prevent "Validating..." messages from flashing when validation completes quickly (< 200ms).

📖 Complete Guide: Custom Control Wrappers

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.

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

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.

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();
});
<!-- 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

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.

// 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

Shape Validation (Development Mode)

In development mode, ngx-vest-forms validates that your form's structure matches your TypeScript model, catching common mistakes early:

// 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: '' },
};
<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

Documentation

Getting Started

Advanced Patterns

UI & Integration

Reference

Examples

  • Examples Project - 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

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

Running Examples Locally

npm install
npm start

Learning Resources

Complex Angular Template-Driven Forms Course - Master advanced form patterns and become a form expert.

Founding Articles by Brecht Billiet

This library was originally created by Brecht Billiet. Here are his foundational blog posts that inspired and guided the development:

Developer Resources

Comprehensive Instruction Files

This project includes detailed instruction files designed to help developers master ngx-vest-forms and Vest.js patterns:

Acknowledgments

🙏 Special thanks to Brecht Billiet 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 - Creator of Vest.js

  • 🎯 The validation engine that powers ngx-vest-forms
  • 🎙️ Featured on PodRocket: Vest with Evyatar Alush - Deep dive into the philosophy and architecture of Vest.js

Ward Bell - Template-Driven Forms Advocate

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 file for details.