ngx-vest-forms 2.0.0-beta.1 → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,113 +1,43 @@
1
1
  <!-- prettier-ignore -->
2
2
  <div align="center">
3
3
 
4
- <img src="./course.jpeg" alt="ngx-vest-forms" align="center" height="96" />
5
-
6
4
  # ngx-vest-forms
7
5
 
6
+ 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.
7
+
8
8
  [![npm version](https://img.shields.io/npm/v/ngx-vest-forms.svg?style=flat-square)](https://www.npmjs.com/package/ngx-vest-forms)
9
9
  [![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)
10
- [![Angular](https://img.shields.io/badge/Angular-18+-dd0031?style=flat-square&logo=angular)](https://angular.dev)
10
+ [![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)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
12
12
  [![License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE)
13
13
 
14
14
  ⭐ If you like this project, star it on GitHub — it helps a lot!
15
15
 
16
- [Overview](#overview) • [Getting Started](#getting-started) • [Complete Example](#complete-example) • [Core Concepts](#core-concepts) • [Validation](#validation) • [Intermediate Topics](#intermediate-topics) • [Advanced Topics](#advanced-topics) • [Features](#features) • [Documentation](#documentation) • [Resources](#resources) • [Developer Resources](#developer-resources) • [Acknowledgments](#acknowledgments)
16
+ [Quick Start](#installation--quick-start) • [Docs](#documentation) • [Key Features](#key-features) • [Migration](#migration) • [FAQ](#faq) • [Resources](#resources)
17
17
 
18
18
  </div>
19
19
 
20
- > [!NOTE]
21
- > **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!
22
-
23
- > [!TIP]
24
- > **What's New**: Major improvements in the latest release! See **[Release Notes](./docs/PR-60-CHANGES.md)** for complete details.
25
- >
26
- > - ✅ **Critical Fix**: Validation timing with `omitWhen` + `validationConfig`
27
- > - ✅ **New Types**: `NgxDeepPartial`, `NgxDeepRequired`, `NgxVestSuite` (cleaner API)
28
- > - ✅ **Array Utilities**: Convert arrays to/from objects for template-driven forms
29
- > - ✅ **Field Path Helpers**: Parse and stringify field paths for Standard Schema
30
-
31
- > [!WARNING]
32
- > **Breaking Change**: You must now call `only()` **unconditionally** in validation suites.
33
- >
34
- > **Migration**: Remove `if (field)` wrapper around `only()` calls:
20
+ > **New Maintainer**:
35
21
  >
36
- > ```typescript
37
- > // ❌ OLD (will break)
38
- > if (field) {
39
- > only(field);
40
- > }
41
- >
42
- > // ✅ NEW (required)
43
- > only(field); // Safe: only(undefined) runs all tests
44
- > ```
45
- >
46
- > **Why**: Conditional `only()` calls corrupt Vest's execution tracking, breaking `omitWhen` + `validationConfig`.
47
- >
48
- > **See**: [Performance Optimization with `only()`](#performance-optimization-with-only) section and [Migration Guide](./docs/PR-60-CHANGES.md#migration-guide) for complete details.
49
-
50
- > [!NOTE]
51
- > **Selector Prefix Update**: We now recommend using the `ngx-` prefix for all selectors. The legacy `sc-` prefix is **deprecated** and will be removed in v3.0.0.
52
- >
53
- > - ✅ **Recommended**: `<ngx-control-wrapper>`, `ngxVestForm`, `ngxValidateRootForm`
54
- > - ⚠️ **Deprecated**: `<sc-control-wrapper>`, `scVestForm`, `validateRootForm`
55
- >
56
- > Both prefixes work in v2.0+, allowing gradual migration. See [Dual Selector Support](./docs/dev/DUAL-SELECTOR-SUPPORT.md) for complete migration guide.
57
-
58
- 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 zero boilerplate.
59
-
60
- > [!TIP]
61
- > **For Developers**: This project includes comprehensive instruction files for GitHub Copilot and detailed development guides. See [Developer Resources](#developer-resources) to copy these files to your workspace for enhanced development experience.
22
+ > 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!
62
23
 
63
- ## Overview
24
+ ## Why ngx-vest-forms?
64
25
 
65
- **ngx-vest-forms** transforms Angular template-driven forms into a powerful, type-safe solution for complex form scenarios. By combining Angular's simplicity with Vest.js's validation power, you get:
26
+ - Unidirectional state with Angular signals
27
+ - Type-safe template-driven forms with runtime shape validation (dev only)
28
+ - Powerful Vest.js validations (sync/async, conditional, composable)
29
+ - Minimal boilerplate: controls and validation wiring are automatic
66
30
 
67
- - **Unidirectional Data Flow** - Predictable state management with Angular signals
68
- - **Type Safety** - Full TypeScript support with runtime shape validation
69
- - **Async Validations** - Built-in support for complex, conditional validations
70
- - **Zero Boilerplate** - Automatic form control creation and validation wiring
71
- - **Conditional Logic** - Show/hide fields and validation rules dynamically
72
- - **Reusable Validations** - Share validation suites across frameworks
31
+ See the full guides under [Documentation](#documentation).
73
32
 
74
- ### Why Choose ngx-vest-forms?
75
-
76
- Traditional Angular reactive forms require extensive boilerplate for complex scenarios. Template-driven forms are simpler but lack type safety and advanced validation features. **ngx-vest-forms bridges this gap**, giving you the best of both worlds.
77
-
78
- ```typescript
79
- // Before: Complex reactive form setup
80
- const form = this.fb.group({
81
- generalInfo: this.fb.group({
82
- firstName: ['', [Validators.required]],
83
- lastName: ['', [Validators.required]]
84
- })
85
- });
86
-
87
- // After: Simple, type-safe template-driven approach
88
- protected readonly formValue = signal<MyFormModel>({});
89
- protected readonly suite = myValidationSuite;
90
- ```
91
-
92
- ## Getting Started
33
+ ## Installation & Quick Start
93
34
 
94
35
  ### Prerequisites
95
36
 
96
- - **Angular**: >=18.0.0 (Signals support required)
37
+ - **Angular**: >=19.0.0 minimum, 20.x recommended (all used APIs stable)
97
38
  - **Vest.js**: >=5.4.6 (Validation engine)
98
39
  - **TypeScript**: >=5.8.0 (Modern Angular features)
99
- - **Node.js**: >=18.19.0 (Required for Angular 18+)
100
-
101
- ### Browser Support
102
-
103
- ngx-vest-forms supports all modern browsers that Angular 18+ targets:
104
-
105
- - **Chrome**: 98+ (includes `structuredClone()` support)
106
- - **Firefox**: 94+ (includes `structuredClone()` support)
107
- - **Safari**: 15.4+ (includes `structuredClone()` support)
108
- - **Edge**: 98+ (includes `structuredClone()` support)
109
-
110
- > **Note**: The library uses native `structuredClone()` for object cloning, which is fully supported in all Angular 18+ target environments. No polyfill is required.
40
+ - **Node.js**: >=20 (Maintenance release)
111
41
 
112
42
  ### Installation
113
43
 
@@ -115,1330 +45,303 @@ ngx-vest-forms supports all modern browsers that Angular 18+ targets:
115
45
  npm install ngx-vest-forms
116
46
  ```
117
47
 
118
- ### Quick Start
119
-
120
- Create your first ngx-vest-forms component in 3 simple steps:
121
-
122
- #### Step 1: Define your form model
123
-
124
- ```typescript
125
- import { signal } from '@angular/core';
126
- import { vestForms, NgxDeepPartial } from 'ngx-vest-forms';
127
-
128
- type MyFormModel = NgxDeepPartial<{
129
- generalInfo: {
130
- firstName: string;
131
- lastName: string;
132
- };
133
- }>;
134
- ```
135
-
136
- #### Step 2: Set up your component
137
-
138
- Use `[ngModel]` (not `[(ngModel)]`) for unidirectional data flow:
139
-
140
- ```typescript
141
- import { vestForms, NgxDeepPartial } from 'ngx-vest-forms';
142
-
143
- // A form model is always deep partial because angular will create it over time organically
144
- type MyFormModel = NgxDeepPartial<{
145
- generalInfo: {
146
- firstName: string;
147
- lastName: string;
148
- };
149
- }>;
150
-
151
- @Component({
152
- imports: [vestForms],
153
- template: `
154
- <form
155
- ngxVestForm
156
- (formValueChange)="formValue.set($event)"
157
- (ngSubmit)="save()"
158
- >
159
- <div ngModelGroup="generalInfo">
160
- <label>First name</label>
161
- <input
162
- type="text"
163
- name="firstName"
164
- [ngModel]="formValue().generalInfo?.firstName"
165
- />
166
-
167
- <label>Last name</label>
168
- <input
169
- type="text"
170
- name="lastName"
171
- [ngModel]="formValue().generalInfo?.lastName"
172
- />
173
- </div>
174
- </form>
175
- `,
176
- })
177
- export class MyComponent {
178
- // This signal will hold the state of our form
179
- protected readonly formValue = signal<MyFormModel>({});
180
- }
181
- ```
182
-
183
- #### Step 3: That's it! 🎉
184
-
185
- Your form automatically creates FormGroups and FormControls with type-safe, unidirectional data flow.
186
-
187
- > [!IMPORTANT]
188
- > Notice we use `[ngModel]` (not `[(ngModel)]`) for unidirectional data flow, and the `?` operator since template-driven forms are `DeepPartial`.
48
+ > **v.2.0.0 NOTE:**
49
+ >
50
+ > You must call `only()` **unconditionally** in Vest suites.
51
+ >
52
+ > ```ts
53
+ > // ✅ Correct
54
+ > only(field); // only(undefined) safely runs all tests
55
+ > ```
56
+ >
57
+ > Why: Conditional `only()` corrupts Vest's execution tracking and breaks `omitWhen` + `validationConfig`.
58
+ > See the [Migration Guide](./docs/migration/MIGRATION-v1.x-to-v2.0.0.md#1-unconditional-only-pattern-required-critical).
59
+ >
60
+ > Selector prefix: use `ngx-` (recommended). The legacy `sc-` works in v2.x but is deprecated and will be removed in v3.
189
61
 
190
- ### Minimal Example
62
+ ### Quick Start
191
63
 
192
- Here's the absolute minimum to get started with ngx-vest-forms:
64
+ Start simple (with validations):
193
65
 
194
- ```typescript
66
+ ```ts
195
67
  import { Component, signal } from '@angular/core';
196
- import { vestForms, NgxDeepPartial } from 'ngx-vest-forms';
197
-
198
- type SimpleForm = NgxDeepPartial<{
199
- email: string;
200
- name: string;
201
- }>;
202
-
203
- @Component({
204
- selector: 'app-simple-form',
205
- imports: [vestForms],
206
- template: `
207
- <form
208
- ngxVestForm
209
- (formValueChange)="formValue.set($event)"
210
- (ngSubmit)="save()"
211
- >
212
- <label for="email">Email</label>
213
- <input id="email" name="email" [ngModel]="formValue().email" />
214
-
215
- <label for="name">Name</label>
216
- <input id="name" name="name" [ngModel]="formValue().name" />
217
-
218
- <button type="submit">Submit</button>
219
- </form>
220
- `,
221
- })
222
- export class SimpleFormComponent {
223
- protected readonly formValue = signal<SimpleForm>({});
224
-
225
- protected save(): void {
226
- console.log('Form submitted:', this.formValue());
227
- }
228
- }
229
- ```
230
-
231
- That's all you need! The `ngxVestForm` directive automatically:
232
-
233
- - Creates FormControls for each input
234
- - Manages form state with signals
235
- - Provides type-safe unidirectional data flow
236
-
237
- ## Complete Example
238
-
239
- A complete working form with ngx-vest-forms requires just 4 steps:
240
-
241
- 1. **Define your form model** using `NgxDeepPartial<T>`
242
- 2. **Create a validation suite** with Vest.js using `staticSuite()`
243
- 3. **Set up your component** with a signal for form state
244
- 4. **Build your template** with `ngxVestForm` directive and `[ngModel]` bindings
245
-
246
- The result: A fully functional form with type safety, automatic form control creation, validation on blur/submit, and error display - all with minimal boilerplate.
247
-
248
- > **📖 Complete Guide**: See **[Complete Example](./docs/COMPLETE-EXAMPLE.md)** for a full working example with detailed explanations of each step, key concepts, and common patterns.
249
-
250
- ## Core Concepts
251
-
252
- ### Understanding Form State
253
-
254
- Angular automatically creates FormGroups and FormControls based on your template structure. The `ngxVestForm` directive provides these outputs:
255
-
256
- | Output | Description |
257
- | ----------------- | ----------------------------------------------- |
258
- | `formValueChange` | Emits when form value changes (debounced) |
259
- | `dirtyChange` | Emits when dirty state changes |
260
- | `validChange` | Emits when validation state changes |
261
- | `errorsChange` | Emits complete list of errors for form/controls |
262
-
263
- ### Public Methods
264
-
265
- | Method | Description |
266
- | ------------------------- | --------------------------------------------------------------------------------------------------------------------- |
267
- | `triggerFormValidation()` | Manually triggers form validation update when form structure changes without value changes (e.g., conditional fields) |
268
-
269
- ### Form Models and Type Safety
270
-
271
- All form models in ngx-vest-forms use `NgxDeepPartial<T>` (or the legacy alias `DeepPartial<T>`) because Angular's template-driven forms build up values incrementally:
272
-
273
- ```typescript
274
- import { NgxDeepPartial } from 'ngx-vest-forms';
275
-
276
- type MyFormModel = NgxDeepPartial<{
277
- generalInfo: {
278
- firstName: string;
279
- lastName: string;
280
- };
281
- }>;
282
- ```
283
-
284
- > **Note**: The `Ngx` prefix prevents naming conflicts with other libraries. Both `NgxDeepPartial` and `DeepPartial` work identically; the Ngx-prefixed version is recommended for new code.
285
-
286
- This is why you must use the `?` operator in templates:
287
-
288
- ```html
289
- <input [ngModel]="formValue().generalInfo?.firstName" name="firstName" />
290
- ```
291
-
292
- ### Validation Suite Type Safety
293
-
294
- Use `NgxVestSuite<T>` for type-safe validation suites:
295
-
296
- ```typescript
297
- import { NgxVestSuite, NgxDeepPartial } from 'ngx-vest-forms';
68
+ import { NgxVestForms, NgxDeepPartial, NgxVestSuite } from 'ngx-vest-forms';
298
69
  import { staticSuite, only, test, enforce } from 'vest';
299
70
 
300
- type FormModel = NgxDeepPartial<{
301
- email: string;
302
- password: string;
303
- profile: {
304
- age: number;
305
- };
306
- }>;
307
-
308
- // Create validation suite
309
- export const validationSuite: NgxVestSuite<FormModel> = staticSuite(
310
- (model: FormModel, field?: string) => {
311
- only(field); // CRITICAL: Always call only() unconditionally
312
-
313
- test('email', 'Email is required', () => {
314
- enforce(model.email).isNotBlank();
315
- });
316
-
317
- test('profile.age', 'Must be 18+', () => {
318
- enforce(model.profile?.age).greaterThanOrEquals(18);
319
- });
320
- }
321
- );
322
-
323
- // Component - use directly
324
- @Component({...})
325
- class MyFormComponent {
326
- protected readonly suite = validationSuite;
327
- protected readonly formValue = signal<FormModel>({});
328
- }
329
- ```
330
-
331
- **Key Benefits:**
332
-
333
- - **Type Safety**: Full TypeScript support for model and field parameters
334
- - **Template Compatibility**: Works seamlessly in Angular templates
335
- - **Simplified API**: Single type for all validation suite needs
336
- - **No Type Assertions**: No `as any` or `$any()` casts needed
337
-
338
- ### Form State Type and Utilities
339
-
340
- The `formState` computed signal returns an `NgxFormState<T>` object with the current form state:
341
-
342
- ```typescript
343
- import { NgxFormState, createEmptyFormState } from 'ngx-vest-forms';
71
+ type MyFormModel = NgxDeepPartial<{ email: string; name: string }>;
344
72
 
345
- // The form state contains:
346
- interface NgxFormState<TModel> {
347
- valid: boolean; // Whether the form is valid
348
- errors: Record<string, string[]>; // Map of field errors by path
349
- value: TModel | null; // Current form value (includes disabled fields)
350
- }
73
+ // Minimal validation suite (always call only(field) unconditionally)
74
+ const suite: NgxVestSuite<MyFormModel> = staticSuite((model, field?) => {
75
+ only(field);
76
+ test('email', 'Email is required', () => {
77
+ enforce(model.email).isNotBlank();
78
+ });
79
+ });
351
80
 
352
- // Useful for parent components displaying child form state
353
81
  @Component({
82
+ imports: [NgxVestForms],
354
83
  template: `
355
- <app-child-form #childForm />
356
- <div>Form Valid: {{ formState().valid }}</div>
357
- <div>Errors: {{ formState().errors | json }}</div>
358
- `,
359
- changeDetection: ChangeDetectionStrategy.OnPush,
360
- })
361
- export class ParentComponent {
362
- // Modern Angular 20+: Use viewChild() instead of @ViewChild
363
- private readonly childForm = viewChild<ChildFormComponent>('childForm');
364
-
365
- // Provide safe fallback when child form isn't initialized yet
366
- protected readonly formState = computed(
367
- () => this.childForm()?.vestForm?.formState() ?? createEmptyFormState()
368
- );
369
- }
370
- ```
371
-
372
- The `createEmptyFormState()` utility creates a safe default state:
373
-
374
- - `valid: true`
375
- - `errors: {}`
376
- - `value: null`
377
-
378
- This prevents null reference errors in templates when child forms or form references might be undefined.
379
-
380
- ### Shape Validation: Catching Typos Early
381
-
382
- Template-driven forms are type-safe, but not in the `name` attributes or `ngModelGroup` attributes.
383
- Making a typo in those can result in a time-consuming endeavor. For this we have introduced shapes.
384
- A shape is an object where the `scVestForm` can validate to. It is a deep required of the form model:
385
-
386
- ```typescript
387
- import { DeepPartial, DeepRequired, vestForms } from 'ngx-vest-forms';
388
-
389
- type MyFormModel = DeepPartial<{
390
- generalInfo: {
391
- firstName: string;
392
- lastName: string;
393
- };
394
- }>;
395
-
396
- export const myFormModelShape: DeepRequired<MyFormModel> = {
397
- generalInfo: {
398
- firstName: '',
399
- lastName: '',
400
- },
401
- };
84
+ <form ngxVestForm [suite]="suite" (formValueChange)="formValue.set($event)">
85
+ <ngx-control-wrapper>
86
+ <label for="email">Email</label>
87
+ <input id="email" name="email" [ngModel]="formValue().email" />
88
+ <!-- Errors display automatically below input -->
89
+ </ngx-control-wrapper>
90
+
91
+ <ngx-control-wrapper>
92
+ <label for="name">Name</label>
93
+ <input id="name" name="name" [ngModel]="formValue().name" />
94
+ </ngx-control-wrapper>
402
95
 
403
- @Component({
404
- imports: [vestForms],
405
- template: `
406
- <form
407
- ngxVestForm
408
- [formShape]="shape"
409
- (formValueChange)="formValue.set($event)"
410
- (ngSubmit)="save()"
411
- >
412
- <div ngModelGroup="generalInfo">
413
- <label>First name</label>
414
- <input
415
- type="text"
416
- name="firstName"
417
- [ngModel]="formValue().generalInformation?.firstName"
418
- />
419
-
420
- <label>Last name</label>
421
- <input
422
- type="text"
423
- name="lastName"
424
- [ngModel]="formValue().generalInformation?.lastName"
425
- />
426
- </div>
96
+ <button type="submit">Submit</button>
427
97
  </form>
428
98
  `,
429
99
  })
430
100
  export class MyComponent {
431
101
  protected readonly formValue = signal<MyFormModel>({});
432
- protected readonly shape = myFormModelShape;
102
+ protected readonly suite = suite;
433
103
  }
434
104
  ```
435
105
 
436
- By passing the shape to the `formShape` input the `ngxVestForm` will validate the actual form value
437
- against the form shape every time the form changes, but only when Angular is in devMode.
438
-
439
- Making a typo in the name attribute or an ngModelGroup attribute would result in runtime errors.
440
- The console would look like this:
441
-
442
- ```chatinput
443
- Error: Shape mismatch:
444
-
445
- [ngModel] Mismatch 'firstame'
446
- [ngModelGroup] Mismatch: 'addresses.billingddress'
447
- [ngModel] Mismatch 'addresses.billingddress.steet'
448
- [ngModel] Mismatch 'addresses.billingddress.number'
449
- [ngModel] Mismatch 'addresses.billingddress.city'
450
- [ngModel] Mismatch 'addresses.billingddress.zipcode'
451
- [ngModel] Mismatch 'addresses.billingddress.country'
452
-
453
-
454
- at validateShape (shape-validation.ts:28:19)
455
- at Object.next (form.directive.ts:178:17)
456
- at ConsumerObserver.next (Subscriber.js:91:33)
457
- at SafeSubscriber._next (Subscriber.js:60:26)
458
- at SafeSubscriber.next (Subscriber.js:31:18)
459
- at subscribe.innerSubscriber (switchMap.js:14:144)
460
- at OperatorSubscriber._next (OperatorSubscriber.js:13:21)
461
- at OperatorSubscriber.next (Subscriber.js:31:18)
462
- at map.js:7:24
463
- ```
464
-
465
- ## Validation
106
+ Notes.
466
107
 
467
- ngx-vest-forms uses [Vest.js](https://vestjs.dev) for validation - a lightweight, flexible validation framework that works across any JavaScript environment.
108
+ - Use `[ngModel]` (not `[(ngModel)]`) for unidirectional data flow
109
+ - The `?` operator is required because template-driven forms build values incrementally (`NgxDeepPartial`)
110
+ - The `name` attribute MUST exactly match the property path used in `[ngModel]` — see [Field Paths](./docs/FIELD-PATHS.md)
468
111
 
469
- ### Creating Your First Validation Suite
112
+ That's all you need. The directive automatically creates controls, wires validation, and manages state.
470
113
 
471
- Vest suites use `staticSuite()` with `only()` for performance optimization:
472
-
473
- ```typescript
474
- import { enforce, only, staticSuite, test } from 'vest';
475
-
476
- export const myFormSuite = staticSuite((model: MyFormModel, field?) => {
477
- only(field); // Call unconditionally at top
478
-
479
- test('firstName', 'First name is required', () => {
480
- enforce(model.firstName).isNotBlank();
481
- });
482
- });
483
- ```
114
+ ## Key Features
484
115
 
485
- **Test parameters**: `test(fieldName, errorMessage, validationFunction)`
116
+ - **Unidirectional state with signals** — Models are `NgxDeepPartial<T>` so values build up incrementally
117
+ - **Type-safe with runtime shape validation** — Automatic control creation and validation wiring (dev mode checks)
118
+ - **Vest.js validations** — Sync/async, conditional, composable patterns with `only(field)` optimization
119
+ - **Error display modes** — Control when errors show: `on-blur`, `on-submit`, or `on-blur-or-submit` (default)
120
+ - **Form state tracking** — Access touched, dirty, valid/invalid states for individual fields or entire form
121
+ - **Error display helpers** — `ngx-control-wrapper`, tokens, and `FormErrorDisplayDirective` for consistent UX
122
+ - **Cross-field dependencies** — `validationConfig` for field-to-field triggers, `ROOT_FORM` for form-level rules
123
+ - **Utilities** — Field paths, field clearing, validation config builder
486
124
 
487
- - Use dot notation for nested fields: `'addresses.billingAddress.street'`
125
+ ### Error Display Modes
488
126
 
489
- ### Connecting Validation to Your Form
490
-
491
- The biggest pain point ngx-vest-forms solves: **Connecting Vest suites to Angular with zero boilerplate**:
127
+ Control when validation errors are shown to users with three built-in modes:
492
128
 
493
129
  ```typescript
494
- // Component
495
- class MyComponent {
496
- protected readonly formValue = signal<MyFormModel>({});
497
- protected readonly suite = myFormModelSuite;
498
- }
499
- ```
500
-
501
- ```html
502
- <!-- Template -->
503
- <form
504
- ngxVestForm
505
- [suite]="suite"
506
- (formValueChange)="formValue.set($event)"
507
- (ngSubmit)="save()"
508
- >
509
- ...
510
- </form>
511
- ```
512
-
513
- That's it! Validations are completely wired. Behind the scenes:
514
-
515
- 1. Control gets created, Angular recognizes the `ngModel` directives
516
- 2. These directives implement `AsyncValidator` and connect to the Vest suite
517
- 3. User types into control
518
- 4. The validate function gets called
519
- 5. Vest returns the errors
520
- 6. ngx-vest-forms puts those errors on the Angular form control
521
-
522
- This means `valid`, `invalid`, `errors`, `statusChanges` all work just like a regular Angular form.
523
-
524
- ### Displaying Validation Errors
130
+ // Global configuration via DI token
131
+ import { NGX_ERROR_DISPLAY_MODE_TOKEN } from 'ngx-vest-forms';
525
132
 
526
- Use the `ngx-control-wrapper` component to show validation errors consistently:
133
+ providers: [
134
+ { provide: NGX_ERROR_DISPLAY_MODE_TOKEN, useValue: 'on-blur-or-submit' }
135
+ ]
527
136
 
528
- ```html
529
- <div ngModelGroup="generalInfo" ngx-control-wrapper>
530
- <ngx-control-wrapper>
531
- <label>First name</label>
532
- <input
533
- type="text"
534
- name="firstName"
535
- [ngModel]="formValue().generalInfo?.firstName"
536
- />
537
- </ngx-control-wrapper>
538
-
539
- <ngx-control-wrapper>
540
- <label>Last name</label>
541
- <input
542
- type="text"
543
- name="lastName"
544
- [ngModel]="formValue().generalInfo?.lastName"
545
- />
546
- </ngx-control-wrapper>
547
- </div>
137
+ // Or per-field via control wrapper
138
+ <ngx-control-wrapper [errorDisplayMode]="'on-blur'">
139
+ <input name="email" [ngModel]="formValue().email" />
140
+ </ngx-control-wrapper>
548
141
  ```
549
142
 
550
- Errors show automatically:
143
+ **Available modes:**
551
144
 
552
- - On blur
553
- - On submit
145
+ - **`on-blur-or-submit`** (default) — Show errors after field is touched OR form is submitted
146
+ - **`on-blur`** Show errors only after field loses focus (touched)
147
+ - **`on-submit`** — Show errors only after form submission
554
148
 
555
- You can use `ngx-control-wrapper` on:
149
+ **Tip**: Use `on-blur-or-submit` for best UX — users get immediate feedback on touched fields while preventing overwhelming errors on pristine forms.
556
150
 
557
- - Elements that hold `ngModelGroup`
558
- - Elements that have an `ngModel` (or form control) inside of them
151
+ 📖 **[Complete Guide: Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)**
559
152
 
560
- ### Performance Optimization with `only()`
153
+ ### Form State
561
154
 
562
- **Critical**: Always call `only()` unconditionally at the top of your validation suite:
563
-
564
- ```typescript
565
- export const suite = staticSuite((model, field?) => {
566
- only(field); // ✅ CORRECT - Unconditional call
567
- // ...tests
568
- });
569
-
570
- // ❌ WRONG - Conditional call
571
- export const badSuite = staticSuite((model, field?) => {
572
- if (field) only(field); // Breaks Vest's execution tracking!
573
- });
574
- ```
575
-
576
- **Why**: `only(undefined)` is safe (runs all tests), while `only('fieldName')` optimizes by running only that field's tests. Conditional calls corrupt Vest's internal state and break `omitWhen` + `validationConfig` timing.
577
-
578
- - ✅ **Fixed**: Proper validation with `omitWhen` and nested fields with `validationConfig`
579
-
580
- > [!IMPORTANT]
581
- > **Critical Pattern**: You MUST call `only()` unconditionally. Never wrap it in `if (field)`. This ensures validation uses current form values and prevents timing issues with `omitWhen` and `validationConfig`. Conditional `only()` calls corrupt Vest's internal execution tracking.
582
-
583
- ### Error Display Control
584
-
585
- The `ngx-control-wrapper` component uses the `FormErrorDisplayDirective` under the hood to manage when and how errors are displayed.
586
-
587
- #### Error Display Modes
588
-
589
- ngx-vest-forms supports three error display modes:
590
-
591
- - **`on-blur-or-submit`** (default) - Show errors after field blur OR form submission
592
- - **`on-blur`** - Show errors only after field blur
593
- - **`on-submit`** - Show errors only after form submission
594
-
595
- #### Configuring Error Display
596
-
597
- **Global Configuration** - Set the default mode for your entire application:
598
-
599
- ```typescript
600
- import { ApplicationConfig } from '@angular/core';
601
- import { NGX_ERROR_DISPLAY_MODE_TOKEN } from 'ngx-vest-forms';
602
-
603
- export const appConfig: ApplicationConfig = {
604
- providers: [{ provide: NGX_ERROR_DISPLAY_MODE_TOKEN, useValue: 'on-submit' }],
605
- };
606
-
607
- // Or in a component
608
- @Component({
609
- providers: [{ provide: NGX_ERROR_DISPLAY_MODE_TOKEN, useValue: 'on-submit' }],
610
- })
611
- export class MyComponent {}
612
- ```
613
-
614
- **Per-Instance Configuration** - Override the mode for specific form fields:
155
+ Access complete form and field state through the `FormErrorDisplayDirective` or `FormControlStateDirective`:
615
156
 
616
157
  ```typescript
617
158
  @Component({
618
159
  template: `
619
- <ngx-control-wrapper [errorDisplayMode]="'on-blur'">
160
+ <ngx-control-wrapper #wrapper="ngxErrorDisplay">
620
161
  <input name="email" [ngModel]="formValue().email" />
621
- </ngx-control-wrapper>
622
- `,
623
- })
624
- export class MyFormComponent {}
625
- ```
626
-
627
- > **Note**: The `ngx-control-wrapper` component accepts `errorDisplayMode` as an input to override the global setting for specific fields.
628
-
629
- ### Validation Options
630
-
631
- You can configure additional `validationOptions` at various levels like `form`, `ngModelGroup` or `ngModel`.
632
-
633
- For example, to debounce validation (useful for API calls):
634
-
635
- ```html
636
- <form ngxVestForm ... [validationOptions]="{ debounceTime: 0 }">
637
- ...
638
- <ngx-control-wrapper>
639
- <label>UserId</label>
640
- <input
641
- type="text"
642
- name="userId"
643
- [ngModel]="formValue().userId"
644
- [validationOptions]="{ debounceTime: 300 }"
645
- />
646
- </ngx-control-wrapper>
647
- ...
648
- </form>
649
- ```
650
-
651
- #### Configurable Validation Config Debounce
652
-
653
- The `validationConfig` feature (for triggering dependent field validations) uses a debounce to prevent excessive validation calls. You can configure this debounce globally or per-component using the `NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN`:
654
-
655
- **Global Configuration** (recommended for consistent behavior):
656
-
657
- ```typescript
658
- import { ApplicationConfig } from '@angular/core';
659
- import { NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN } from 'ngx-vest-forms';
660
-
661
- export const appConfig: ApplicationConfig = {
662
- providers: [
663
- // Set global debounce for validationConfig dependencies
664
- { provide: NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN, useValue: 150 },
665
- ],
666
- };
667
- ```
668
-
669
- **Per-Route Configuration**:
670
-
671
- ```typescript
672
- {
673
- path: 'checkout',
674
- component: CheckoutComponent,
675
- providers: [
676
- { provide: NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN, useValue: 50 }
677
- ]
678
- }
679
- ```
680
-
681
- **Per-Component Configuration**:
682
-
683
- ```typescript
684
- @Component({
685
- providers: [
686
- // Disable debounce for testing
687
- { provide: NGX_VALIDATION_CONFIG_DEBOUNCE_TOKEN, useValue: 0 },
688
- ],
689
- })
690
- export class MyFormComponent {}
691
- ```
692
-
693
- **Default**: 100ms (maintains backward compatibility)
694
-
695
- > **Note**: This token controls the debounce for `validationConfig` dependency triggering only. For individual field validation debouncing, use `[validationOptions]="{ debounceTime: 300 }"` on the control as shown above.
696
-
697
- ## Intermediate Topics
698
-
699
- ### Conditional Fields
700
-
701
- Use computed signals to show/hide fields dynamically. Angular automatically manages FormControl creation/removal:
702
-
703
- ```typescript
704
- class MyComponent {
705
- protected readonly showLastName = computed(
706
- () => !!this.formValue().firstName
707
- );
708
- }
709
- ```
710
-
711
- ```html
712
- @if(showLastName()) {
713
- <input name="lastName" [ngModel]="formValue().lastName" />
714
- }
715
-
716
- <label>Last name</label>
717
- <input
718
- type="text"
719
- name="lastName"
720
- [ngModel]="formValue().generalInformation?.lastName"
721
- />
722
- </div>
723
- }
724
- ```
725
-
726
- ### Reactive Disabling
727
-
728
- To achieve reactive disabling, we just have to take advantage of computed signals as well:
729
-
730
- ```typescript
731
- class MyComponent {
732
- protected readonly lastNameDisabled = computed(
733
- () => !this.formValue().generalInfo?.firstName
734
- );
735
- }
736
- ```
737
-
738
- We can bind the computed signal to the `disabled` directive of Angular.
739
-
740
- ```html
741
- <input
742
- type="text"
743
- name="lastName"
744
- [disabled]="lastNameDisabled()"
745
- [ngModel]="formValue().generalInformation?.lastName"
746
- />
747
- ```
748
-
749
- ### Conditional Validations
750
-
751
- Vest makes it extremely easy to create conditional validations.
752
- Assume we have a form model that has `age` and `emergencyContact`.
753
- The `emergencyContact` is required, but only when the person is not of legal age.
754
-
755
- We can use the `omitWhen` so that when the person is below 18, the assertion
756
- will not be done.
757
-
758
- ```typescript
759
- import { enforce, omitWhen, only, staticSuite, test } from 'vest';
760
-
761
- ...
762
- omitWhen((model.age || 0) >= 18, () => {
763
- test('emergencyContact', 'Emergency contact is required', () => {
764
- enforce(model.emergencyContact).isNotBlank();
765
- });
766
- });
767
- ```
768
-
769
- You can put those validations on every field that you want. On form group fields and on form control fields.
770
- Check this interesting example below:
771
-
772
- - [x] Password is always required
773
- - [x] Confirm password is only required when there is a password
774
- - [x] The passwords should match, but only when they are both filled in
775
-
776
- ````typescript
777
- test('passwords.password', 'Password is not filled in', () => {
778
- enforce(model.passwords?.password).isNotBlank();
779
- });
780
-
781
- omitWhen(!model.passwords?.password, () => {
782
- test('passwords.confirmPassword', 'Confirm password required', () => {
783
- enforce(model.passwords?.confirmPassword).isNotBlank();
784
- });
785
-
786
- test('passwords', 'Passwords must match', () => {
787
- enforce(model.passwords?.confirmPassword).equals(model.passwords?.password);
788
- });
789
- });
790
-
791
- This pattern is testable, reusable across frameworks, and readable.
792
162
 
793
- Forget about manually adding, removing validators on reactive forms and not being able to
794
- re-use them. This code is easy to test, easy to re-use on frontend, backend, angular, react, etc...
795
- **Oh, it's also pretty readable**
796
-
797
- ### Dependent Field Validation with Conditional Rendering
798
-
799
- When fields that depend on each other are conditionally rendered (using `@if`), you need a **reactive validationConfig** to avoid "control not found" warnings.
800
-
801
- > **💡 Related Pattern**: If you're also switching between form inputs and non-form content (like informational text), you'll need [`triggerFormValidation()`](#handling-form-structure-changes) in addition to computed `validationConfig`. See the [comparison matrix](#handling-dynamic-forms-two-common-patterns) for when to use each.
802
-
803
- #### The Problem
804
-
805
- ```typescript
806
- // ❌ This causes warnings when conditional fields don't exist
807
- class MyComponent {
808
- protected readonly validationConfig = {
809
- quantity: ['justification'], // justification only shows when quantity > 5!
810
- };
811
- }
812
- ````
813
-
814
- #### The Solution
815
-
816
- Use a **computed signal** for `validationConfig`:
817
-
818
- ```typescript
819
- import { Component, signal, computed } from '@angular/core';
820
-
821
- @Component({
822
- template: `
823
- <form ngxVestForm [validationConfig]="validationConfig()" ...>
824
- <input name="quantity" [ngModel]="formValue().quantity" />
825
-
826
- @if ((formValue().quantity || 0) > 5) {
827
- <textarea
828
- name="justification"
829
- [ngModel]="formValue().justification"
830
- ></textarea>
163
+ @if (wrapper.isTouched()) {
164
+ <span>Field was touched</span>
831
165
  }
832
- </form>
833
- `,
166
+ @if (wrapper.isPending()) {
167
+ <span>Validating...</span>
168
+ }
169
+ </ngx-control-wrapper>
170
+ `
834
171
  })
835
- export class MyComponent {
836
- protected readonly formValue = signal<MyFormModel>({});
837
-
838
- // ✅ Computed config only references controls that exist
839
- protected readonly validationConfig = computed(() => {
840
- const config: Record<string, string[]> = {};
841
-
842
- // Only add dependency when field is in DOM
843
- if ((this.formValue().quantity || 0) > 5) {
844
- config['quantity'] = ['justification'];
845
- config['justification'] = ['quantity']; // Bidirectional validation
846
- }
847
-
848
- return config;
849
- });
850
- }
851
172
  ```
852
173
 
853
- **Key Points:**
174
+ **Available state signals:**
854
175
 
855
- - **Reactive**: Config updates when field visibility changes
856
- - **No Warnings**: Only references controls that exist in DOM
857
- - **Bidirectional**: Works for two-way validation dependencies
858
- - **Type-Safe**: Full TypeScript support
176
+ - `isTouched()` / `isDirty()` User interaction state
177
+ - `isValid()` / `isInvalid()` Validation state
178
+ - `isPending()` Async validation in progress
179
+ - `errorMessages()` / `warningMessages()` Current validation messages
180
+ - `shouldShowErrors()` — Computed based on display mode and state
859
181
 
860
- **Template Binding:**
182
+ **Tip**: For async validations, use `createDebouncedPendingState()` to prevent "Validating..." messages from flashing when validation completes quickly (< 200ms).
861
183
 
862
- ```html
863
- <!-- Notice the function call: validationConfig() -->
864
- <form ngxVestForm [validationConfig]="validationConfig()" ...>...</form>
865
- ```
184
+ 📖 **[Complete Guide: Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)**
866
185
 
867
- ### ValidationConfig Fluent Builder API
186
+ ## Advanced Features
868
187
 
869
- For complex validation configurations, ngx-vest-forms provides a type-safe fluent builder API that makes validation dependencies clearer and easier to maintain.
188
+ ### Validation Config
870
189
 
871
- #### Why Use the Builder?
190
+ Automatically re-validate dependent fields when another field changes. Essential when using Vest.js's `omitWhen`/`skipWhen` for conditional validations.
872
191
 
873
- **Without Builder (Manual):**
192
+ **When to use**: Password confirmation, conditional required fields, or any field that depends on another field's value.
874
193
 
875
194
  ```typescript
876
- // ❌ Verbose, error-prone, no type safety
877
195
  protected readonly validationConfig = {
878
- 'password': ['confirmPassword'],
879
- 'confirmPassword': ['password'], // Easy to forget reverse
880
- 'startDate': ['endDate'],
881
- 'endDate': ['startDate'],
882
- 'country': ['state', 'zipCode'],
196
+ 'password': ['confirmPassword'], // When password changes, re-validate confirmPassword
197
+ 'age': ['emergencyContact'] // When age changes, re-validate emergencyContact
883
198
  };
884
199
  ```
885
200
 
886
- **With Builder:**
887
-
888
- ```typescript
889
- import { createValidationConfig } from 'ngx-vest-forms';
890
-
891
- // ✅ Clean, type-safe, self-documenting
892
- protected readonly validationConfig = createValidationConfig<MyFormModel>()
893
- .bidirectional('password', 'confirmPassword')
894
- .bidirectional('startDate', 'endDate')
895
- .whenChanged('country', ['state', 'zipCode'])
896
- .build();
897
- ```
898
-
899
- #### Builder Methods
900
-
901
- **`whenChanged(trigger, revalidate)`** - One-way dependency
902
-
903
- ```typescript
904
- // When country changes, revalidate state and zipCode
905
- .whenChanged('country', ['state', 'zipCode'])
906
- ```
201
+ **Important**: `validationConfig` only triggers re-validation—validation logic is always defined in your Vest suite.
907
202
 
908
- **`bidirectional(field1, field2)`** - Two-way dependency
203
+ 📖 **[Complete Guide: ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)**
909
204
 
910
- ```typescript
911
- // When either password or confirmPassword changes, revalidate the other
912
- .bidirectional('password', 'confirmPassword')
913
- ```
205
+ ### Root-Form Validation
914
206
 
915
- **`group(fields)`** - All fields revalidate each other
916
-
917
- ```typescript
918
- // When any contact field changes, revalidate all others
919
- .group(['firstName', 'lastName', 'email'])
920
- ```
921
-
922
- **`merge(config)`** - Combine configurations
923
-
924
- ```typescript
925
- // Conditionally merge additional config
926
- .merge(isInternational() ? { country: ['customsForm'] } : {})
927
- ```
207
+ Form-level validation rules that don't belong to any specific field (e.g., "at least one contact method required").
928
208
 
929
- #### Real-World Example
209
+ **When to use**: Business rules that evaluate multiple fields but errors should appear at form level, not on individual fields.
930
210
 
931
211
  ```typescript
932
- import { Component, signal, computed } from '@angular/core';
933
- import { createValidationConfig, type DeepPartial } from 'ngx-vest-forms';
934
-
935
- type OrderFormModel = DeepPartial<{
936
- // Customer info
937
- firstName: string;
938
- lastName: string;
939
- email: string;
940
-
941
- // Password
942
- password: string;
943
- confirmPassword: string;
944
-
945
- // Dates
946
- startDate: Date;
947
- endDate: Date;
948
-
949
- // Location
950
- country: string;
951
- state: string;
952
- zipCode: string;
953
- }>;
954
-
955
- @Component({
956
- // ...
957
- })
958
- export class OrderFormComponent {
959
- protected readonly validationConfig = createValidationConfig<OrderFormModel>()
960
- // Customer info group
961
- .group(['firstName', 'lastName', 'email'])
962
-
963
- // Password confirmation
964
- .bidirectional('password', 'confirmPassword')
965
-
966
- // Date range
967
- .bidirectional('startDate', 'endDate')
968
-
969
- // Location dependencies
970
- .whenChanged('country', ['state', 'zipCode'])
971
-
972
- .build();
973
- }
974
- ```
975
-
976
- #### Benefits
977
-
978
- - ✅ **Type Safety**: IDE autocomplete for all field paths
979
- - ✅ **Readability**: Intent is clear from method names
980
- - ✅ **Maintainability**: Less boilerplate, easier to understand
981
- - ✅ **Error Prevention**: Compile-time validation of field names
982
- - ✅ **Backward Compatible**: Works alongside manual configurations
983
-
984
- #### Combining with Computed Signals
985
-
986
- For dynamic configurations, combine the builder with computed signals:
212
+ import { ROOT_FORM } from 'ngx-vest-forms';
987
213
 
988
- ```typescript
989
- protected readonly validationConfig = computed(() =>
990
- createValidationConfig<FormModel>()
991
- .bidirectional('password', 'confirmPassword')
992
- // Conditionally add dependencies
993
- .merge(
994
- this.isInternational()
995
- ? { country: ['customsForm', 'taxId'] }
996
- : {}
997
- )
998
- .build()
999
- );
214
+ // In your Vest suite
215
+ test(ROOT_FORM, 'At least one contact method is required', () => {
216
+ enforce(model.email || model.phone).isTruthy();
217
+ });
1000
218
  ```
1001
219
 
1002
- > **📚 Learn More**: See the [ValidationConfig Builder Guide](./docs/VALIDATION-CONFIG-BUILDER.md) for comprehensive examples and patterns.
1003
-
1004
- ## Advanced Topics
1005
-
1006
- ### Handling Dynamic Forms: Two Common Patterns
1007
-
1008
- Dynamic forms present two distinct challenges that require different solutions:
1009
-
1010
- | Challenge | Solution | When to Use |
1011
- | --------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
1012
- | **Conditional fields with validation dependencies** | [Computed `validationConfig`](#dependent-field-validation-with-conditional-rendering) | Fields depend on each other AND are conditionally rendered |
1013
- | **Structure changes without value changes** | [`triggerFormValidation()`](#handling-form-structure-changes) | Switching between form inputs and non-form content |
1014
-
1015
- > **💡 Pro Tip**: These solutions are complementary and often used together in complex forms with both conditional validation dependencies and dynamic structure changes.
1016
-
1017
- ### Handling Form Structure Changes
1018
-
1019
- When form structure changes dynamically (e.g., switching between form inputs and non-form elements like `<p>` tags), use `triggerFormValidation()` to manually update validation state:
1020
-
1021
- ```typescript
1022
- @Component({
1023
- template: `
1024
- <form ngxVestForm [suite]="suite" #vestForm="ngxVestForm">
1025
- <select
1026
- name="type"
1027
- [ngModel]="formValue().type"
1028
- (ngModelChange)="onTypeChange($event)"
1029
- >
1030
- <option value="typeA">Type A</option>
1031
- <option value="typeC">Type C (no input)</option>
1032
- </select>
1033
-
1034
- @if (formValue().type === 'typeA') {
1035
- <input name="fieldA" [ngModel]="formValue().fieldA" />
1036
- } @else {
1037
- <p>No additional input required.</p>
1038
- }
1039
- </form>
1040
- `,
1041
- })
1042
- class MyComponent {
1043
- protected readonly vestFormRef = viewChild.required('vestForm', {
1044
- read: FormDirective,
1045
- });
1046
-
1047
- protected onTypeChange(type: string) {
1048
- this.formValue.update((v) => {
1049
- const updated = clearFieldsWhen(v, { fieldA: type !== 'typeA' });
1050
- return { ...updated, type };
1051
- });
1052
- this.vestFormRef().triggerFormValidation();
1053
- }
1054
- }
220
+ ```html
221
+ <!-- In template -->
222
+ <form ngxVestForm ngxValidateRootForm [suite]="suite">
223
+ <!-- Show form-level errors -->
224
+ <div *ngIf="vestForm.errors?.rootForm">{{ vestForm.errors.rootForm }}</div>
225
+ </form>
1055
226
  ```
1056
227
 
1057
- > **📖 Detailed Guide**: See **[Field Clearing Utilities](./docs/FIELD-CLEARING-UTILITIES.md)** and **[Structure Change Detection Guide](./docs/STRUCTURE_CHANGE_DETECTION.md)** for comprehensive examples.
1058
-
1059
- **When to use `triggerFormValidation()`**: After form structure changes (showing/hiding fields), clearing form sections, or switching between form inputs and non-form content.
228
+ 📖 **[Complete Guide: ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)**
1060
229
 
1061
- > **🔗 See Also**: [Computed `validationConfig`](#dependent-field-validation-with-conditional-rendering) for validation dependencies, and [Field Clearing Utilities](./docs/FIELD-CLEARING-UTILITIES.md) for state management.
230
+ ### Dynamic Form Structure
1062
231
 
1063
- #### Field Clearing Pattern
232
+ Manually trigger validation when form structure changes between **input fields and non-input content** (like `<p>` tags) without value changes.
1064
233
 
1065
- **When field clearing is required**: When switching between form inputs and non-form elements (e.g., `<input>` `<p>` tags).
234
+ **When to use**: When switching from form controls to informational text/paragraphs where no control values change.
1066
235
 
1067
- **Why**: Angular removes FormControls when switching to non-form content, but component signals retain old values, creating state inconsistency.
236
+ **NOT needed when**: Switching between different input fields (value changes trigger validation automatically).
1068
237
 
1069
238
  ```typescript
1070
- // Use clearFieldsWhen to synchronize state
1071
- import { clearFieldsWhen } from 'ngx-vest-forms';
1072
-
1073
- this.formValue.update((v) =>
1074
- clearFieldsWhen(v, {
1075
- fieldA: procedureType !== 'typeA', // Clear when NOT showing input
1076
- })
1077
- );
1078
- ```
1079
-
1080
- **When NOT required**: Pure form-to-form conditionals (switching input types with same `name`) – Angular maintains FormControl throughout.
1081
-
1082
- ##### When Field Clearing is NOT Required
1083
-
1084
- Pure form-to-form conditionals (switching input types with same `name`) usually don't need field clearing because Angular maintains the FormControl throughout:
1085
-
1086
- ```typescript
1087
- // These switches DON'T require field clearing:
1088
- @if (inputType === 'text') {
1089
- <input name="field" [ngModel]="formValue().field" type="text" />
239
+ // Example: Switching from input to paragraph
240
+ @if (type() === 'typeA') {
241
+ <input name="fieldA" [ngModel]="formValue().fieldA" />
1090
242
  } @else {
1091
- <input name="field" [ngModel]="formValue().field" type="number" />
243
+ <p>No input required</p> // ← No form control, needs triggerFormValidation()
1092
244
  }
1093
- ```
1094
-
1095
- > **📖 Complete Guide**: See **[Field Clearing Utilities](./docs/FIELD-CLEARING-UTILITIES.md)** for detailed patterns and use cases.
1096
-
1097
- ### Field State Utilities
1098
-
1099
- When building dynamic forms, you often need to conditionally show/hide form inputs or switch between form inputs and non-form content (like informational text). ngx-vest-forms provides specialized utilities to keep your component state synchronized with Angular's form state during these transitions.
1100
-
1101
- **Key Utilities:**
1102
-
1103
- - **`clearFieldsWhen(state, conditions)`** - Conditionally clear fields based on boolean conditions (most common)
1104
- - **`clearFields(state, fieldArray)`** - Unconditionally clear specific fields (for reset operations)
1105
- - **`keepFieldsWhen(state, conditions)`** - Keep only fields that meet conditions (whitelist approach)
1106
-
1107
- **When to Use:**
1108
-
1109
- These utilities are primarily needed when your template conditionally renders form inputs in some branches and non-form content (like `<p>` tags) in others. They ensure your component state stays consistent with the actual form structure.
1110
-
1111
- **Example:**
1112
-
1113
- ```typescript
1114
- import { clearFieldsWhen } from 'ngx-vest-forms';
1115
-
1116
- // Clear shipping address when switching from input to "No shipping needed" message
1117
- const updatedState = clearFieldsWhen(formValue(), {
1118
- 'addresses.shippingAddress': !useShippingAddress,
1119
- emergencyContact: age >= 18, // Clear when adult (no emergency contact input shown)
1120
- });
1121
- ```
1122
-
1123
- **Important Note:**
1124
-
1125
- Pure form-to-form conditionals (switching between different input types with the same `name`) typically don't require these utilities as Angular maintains the FormControl throughout the transition.
1126
-
1127
- > **📖 Detailed Guide**: See **[Field Clearing Utilities](./docs/FIELD-CLEARING-UTILITIES.md)** for comprehensive examples, use cases, and when field clearing is required vs. not required.
1128
-
1129
- ### Composable Validations
1130
245
 
1131
- Break down complex validation logic into reusable functions that can be shared across forms, frameworks, and even frontend/backend:
1132
-
1133
- ```typescript
1134
- // Reusable validation function
1135
- export function addressValidations(
1136
- model: AddressModel | undefined,
1137
- field: string
1138
- ): void {
1139
- test(`${field}.street`, 'Street is required', () => {
1140
- enforce(model?.street).isNotBlank();
1141
- });
1142
- test(`${field}.city`, 'City is required', () => {
1143
- enforce(model?.city).isNotBlank();
1144
- });
1145
- // ... more validations
246
+ onTypeChange(newType: string) {
247
+ this.formValue.update(v => ({ ...v, type: newType }));
248
+ this.vestForm.triggerFormValidation(); // Only needed for structure changes
1146
249
  }
1147
-
1148
- // Use in your suite
1149
- export const orderSuite: NgxVestSuite<OrderFormModel> = staticSuite(
1150
- (model, field?) => {
1151
- only(field);
1152
- addressValidations(model.billingAddress, 'billingAddress');
1153
- addressValidations(model.shippingAddress, 'shippingAddress');
1154
- }
1155
- );
1156
250
  ```
1157
251
 
1158
- **Benefits:**
1159
-
1160
- - ✅ **Reusability** - Share validation logic across different forms
1161
- - ✅ **Maintainability** - Update validation logic in one place
1162
- - ✅ **Testability** - Test validation functions independently
1163
- - ✅ **Cross-framework** - Use same logic on frontend/backend, Angular/React
252
+ 📖 **[Complete Guide: Structure Change Detection](./docs/STRUCTURE_CHANGE_DETECTION.md)**
1164
253
 
1165
- > **📖 Detailed Guide**: See **[Composable Validations](./docs/COMPOSABLE-VALIDATIONS.md)** for advanced patterns, conditional composition, organizing validation files, nested compositions, and testing strategies.
254
+ ### Shape Validation (Development Mode)
1166
255
 
1167
- ### Custom Control Wrappers
1168
-
1169
- Create your own error display components using the `FormErrorDisplayDirective`, which provides all the validation state you need:
256
+ In development mode, ngx-vest-forms validates that your form's structure matches your TypeScript model, catching common mistakes early:
1170
257
 
1171
258
  ```typescript
1172
- @Component({
1173
- selector: 'app-custom-wrapper',
1174
- hostDirectives: [FormErrorDisplayDirective],
1175
- template: `
1176
- <div class="field-wrapper">
1177
- <ng-content />
1178
- @if (errorDisplay.shouldShowErrors()) {
1179
- <div class="error">
1180
- @for (error of errorDisplay.errors(); track error) {
1181
- <span>{{ error }}</span>
1182
- }
1183
- </div>
1184
- }
1185
- @if (errorDisplay.isPending()) {
1186
- <div class="validating">Validating...</div>
1187
- }
1188
- </div>
1189
- `,
1190
- })
1191
- export class CustomWrapperComponent {
1192
- protected readonly errorDisplay = inject(FormErrorDisplayDirective, {
1193
- self: true,
1194
- });
1195
- }
1196
- ```
1197
-
1198
- **When to create custom wrappers:**
1199
-
1200
- - Match your design system (Material, PrimeNG, etc.)
1201
- - Custom error formatting (tooltips, popovers, inline)
1202
- - Add UI elements (icons, help text, character counters)
1203
- - Specific accessibility patterns
1204
-
1205
- > **📖 Detailed Guide**: See **[Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)** for Material Design examples, available signals reference, and best practices.
1206
-
1207
- ### Cross-Field Validation: Three Complementary Features
1208
-
1209
- ngx-vest-forms provides three features for handling validation in complex, dynamic forms:
1210
-
1211
- > **💡 Key Insight**: `validationConfig` is **essential** when using Vest.js's `omitWhen`/`skipWhen` for conditional validations. It ensures Angular re-validates dependent fields when conditions change.
1212
-
1213
- | Feature | Purpose | Errors Appear At | Use For |
1214
- | ----------------------------- | ----------------------------------------------- | ------------------------------------ | ------------------------------------------------- |
1215
- | **`validationConfig`** | **Re-validation trigger** when fields change | **Field level** (`errors.fieldName`) | Fields that need re-validation when others change |
1216
- | **`validateRootForm`** | Creates **form-level** validations | **Form level** (`errors.rootForm`) | Form-wide business rules |
1217
- | **`triggerFormValidation()`** | Manual validation trigger for structure changes | N/A (triggers existing validations) | Structure changes without value changes |
1218
-
1219
- **Key Insight**: These solve different problems and often work together!
1220
-
1221
- > **📖 Complete Guide**: See **[Validation Features Guide](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)** for detailed comparison, decision trees, use cases, and examples of using all three features together.
1222
-
1223
- **Quick Examples:**
259
+ // Your model
260
+ type MyFormModel = NgxDeepPartial<{
261
+ email: string;
262
+ address: { street: string; city: string };
263
+ }>;
1224
264
 
1225
- ```typescript
1226
- // validationConfig: Re-validation trigger (NOT validation logic!)
1227
- // This says: "When password changes, also re-validate confirmPassword"
1228
- validationConfig = {
1229
- password: ['confirmPassword'], // When password changes, revalidate confirmPassword
265
+ // Define shape for runtime validation
266
+ const shape: NgxDeepRequired<MyFormModel> = {
267
+ email: '',
268
+ address: { street: '', city: '' },
1230
269
  };
1231
- // The actual validation logic is in your Vest suite:
1232
- test('confirmPassword', 'Must match', () => {
1233
- enforce(model.confirmPassword).equals(model.password);
1234
- });
1235
-
1236
- // validateRootForm: Form-level business rules
1237
- test(ROOT_FORM, 'Brecht is not 30 anymore', () => {
1238
- enforce(
1239
- model.firstName === 'Brecht' &&
1240
- model.lastName === 'Billiet' &&
1241
- model.age === 30
1242
- ).isFalsy();
1243
- });
1244
-
1245
- // triggerFormValidation(): After structure changes
1246
- protected readonly vestFormRef = viewChild.required('vestForm', { read: FormDirective });
1247
-
1248
- onTypeChange(type: string) {
1249
- this.formValue.update(v => ({ ...v, type }));
1250
- this.vestFormRef().triggerFormValidation(); // ✅ Force validation update
1251
- }
1252
- ```
1253
-
1254
- ### Validations on the Root Form
1255
-
1256
- For form-level validations that span multiple fields, use the `ROOT_FORM` constant with `validateRootForm`:
1257
-
1258
- > **⚠️ Breaking Change (v3)**: Default validation mode changed from `'live'` to `'submit'`. See [Migration Guide](./docs/MIGRATION-V3.md) for details.
1259
-
1260
- ```typescript
1261
- import { ROOT_FORM } from 'ngx-vest-forms';
1262
-
1263
- // In your suite
1264
- test(ROOT_FORM, 'Passwords must match', () => {
1265
- enforce(model.confirmPassword).equals(model.password);
1266
- });
1267
270
  ```
1268
271
 
1269
272
  ```html
1270
- <form
1271
- ngxVestForm
1272
- ngxValidateRootForm
1273
- [ngxValidateRootFormMode]="'submit'"
1274
- [suite]="suite"
1275
- (errorsChange)="errors.set($event)"
1276
- >
1277
- <!-- Display root-level errors -->
1278
- {{ errors()?.['rootForm'] }}
1279
- </form>
1280
- ```
1281
-
1282
- #### Validation Modes
1283
-
1284
- Root form validation supports two modes:
273
+ <form ngxVestForm [suite]="suite" [formShape]="shape">
274
+ <!-- ✅ Correct: matches shape -->
275
+ <input name="email" [ngModel]="formValue().email" />
276
+ <input name="address.street" [ngModel]="formValue().address?.street" />
1285
277
 
1286
- - **`'submit'` (default)**: Validates only after the form is submitted. This is the recommended mode for most use cases as it prevents premature validation errors.
1287
- - **`'live'`**: Validates on every value change, providing immediate feedback.
278
+ <!-- Error in dev mode: typo detected -->
279
+ <input name="emial" [ngModel]="formValue().email" />
1288
280
 
1289
- ```html
1290
- <!-- Submit mode - validates after submit (default) -->
1291
- <form ngxVestForm ngxValidateRootForm [ngxValidateRootFormMode]="'submit'">
1292
- <!-- form controls -->
1293
- </form>
1294
-
1295
- <!-- Live mode - validates immediately -->
1296
- <form ngxVestForm ngxValidateRootForm [ngxValidateRootFormMode]="'live'">
1297
- <!-- form controls -->
281
+ <!-- ❌ Error in dev mode: path doesn't exist in shape -->
282
+ <input name="address.zipcode" [ngModel]="formValue().address?.zipcode" />
1298
283
  </form>
1299
284
  ```
1300
285
 
1301
- ### Validation of Dependent Controls
1302
-
1303
- When field validations depend on other fields (e.g., `confirmPassword` depends on `password`), use `validationConfig` to trigger re-validation:
1304
-
1305
- > **📖 When to use `validationConfig` vs `validateRootForm`**: See **[ValidationConfig vs ROOT_FORM Validation](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)** for a complete comparison and decision tree.
1306
-
1307
- ```typescript
1308
- // In your suite - Vest handles the logic
1309
- omitWhen(!model.password, () => {
1310
- test('confirmPassword', 'Passwords must match', () => {
1311
- enforce(model.confirmPassword).equals(model.password);
1312
- });
1313
- });
1314
- ```
1315
-
1316
- ```typescript
1317
- // In your component - validationConfig handles Angular orchestration
1318
- protected validationConfig = {
1319
- password: ['confirmPassword'] // When password changes, revalidate confirmPassword
1320
- };
1321
- ```
1322
-
1323
- ```html
1324
- <form ngxVestForm [suite]="suite" [validationConfig]="validationConfig">
1325
- <input name="password" [ngModel]="formValue().password" />
1326
- <input name="confirmPassword" [ngModel]="formValue().confirmPassword" />
1327
- </form>
1328
- ```
286
+ **Benefits:**
1329
287
 
1330
- **Why `validationConfig` is needed**: Vest.js handles validation logic, but cannot tell Angular to revalidate other form controls. The `validationConfig` bridges this gap by telling Angular's form system which fields should be revalidated when others change.
1331
- There is also a complex example of form arrays with complex validations in the examples.
288
+ - Catch typos in `name` attributes immediately during development
289
+ - Ensure template structure matches TypeScript model
290
+ - Zero runtime cost in production (checks disabled automatically)
291
+ - Works with nested objects and arrays
1332
292
 
1333
- ### Child Form Components
293
+ **Important**: Shape validation only runs in development mode (`isDevMode()` returns `true`). Production builds have zero overhead.
1334
294
 
1335
- Large forms are difficult to maintain in a single file. ngx-vest-forms supports splitting forms into reusable child components, which is essential for code organization and component reusability (like address forms used in multiple places).
295
+ 📖 **[Complete Guide: Field Paths](./docs/FIELD-PATHS.md)**
1336
296
 
1337
- **Critical Requirement:**
297
+ ## Documentation
1338
298
 
1339
- Child components that contain form fields **MUST** use `vestFormsViewProviders` to access the parent form:
299
+ ### Getting Started
1340
300
 
1341
- ```typescript
1342
- import { Component, input } from '@angular/core';
1343
- import { vestForms, vestFormsViewProviders } from 'ngx-vest-forms';
301
+ - **[Complete Example](./docs/COMPLETE-EXAMPLE.md)** - Step-by-step walkthrough from basic form to advanced patterns
302
+ - **[Composable Validations](./docs/COMPOSABLE-VALIDATIONS.md)** - Break validation logic into reusable, testable functions
1344
303
 
1345
- @Component({
1346
- selector: 'app-address',
1347
- viewProviders: [vestFormsViewProviders], // ⚠️ REQUIRED for child form components
1348
- template: `
1349
- <ngx-control-wrapper>
1350
- <label>Street</label>
1351
- <input [ngModel]="address().street" name="street" />
1352
- </ngx-control-wrapper>
1353
- <!-- More address fields... -->
1354
- `,
1355
- })
1356
- export class AddressComponent {
1357
- readonly address = input<AddressModel>();
1358
- }
1359
- ```
304
+ ### Advanced Patterns
1360
305
 
1361
- **Key Benefits:**
306
+ - **[ValidationConfig vs Root-Form](./docs/VALIDATION-CONFIG-VS-ROOT-FORM.md)** - Cross-field dependencies and form-level rules
307
+ - **[Field Path Types](./docs/FIELD-PATHS.md)** - Type-safe dot-notation paths for nested properties
308
+ - **[Structure Change Detection](./docs/STRUCTURE_CHANGE_DETECTION.md)** - Handle dynamic form structure updates
309
+ - **[Field Clearing Utilities](./docs/FIELD-CLEARING-UTILITIES.md)** - Type-safe utilities for clearing nested form values
1362
310
 
1363
- - **Component Reusability** - Share address, phone number, or other form sections across multiple forms
1364
- - **Code Organization** - Keep large forms manageable by splitting them into logical sections
1365
- - **Type Safety** - Each child component can have its own strongly-typed model
1366
- - **Dynamic Field Names** - Use inputs to customize field name prefixes (e.g., `billingAddress.street` vs `shippingAddress.street`)
311
+ ### UI & Integration
1367
312
 
1368
- > **📖 Detailed Guide**: See **[Child Components](./docs/CHILD-COMPONENTS.md)** for complete examples including dynamic field names, nested child components, and troubleshooting common issues.
313
+ - **[Child Components](./docs/CHILD-COMPONENTS.md)** - Split large forms into smaller, maintainable components
314
+ - **[Custom Control Wrappers](./docs/CUSTOM-CONTROL-WRAPPERS.md)** - Build consistent error display patterns
315
+ - **[API Tokens](./docs/API-TOKENS.md)** - Configure error display modes and other global settings
1369
316
 
1370
- ## Features
317
+ ### Reference
1371
318
 
1372
- Now that you've seen how ngx-vest-forms works, here's a complete overview of its capabilities:
319
+ - **[Utilities README](./projects/ngx-vest-forms/src/lib/utils/README.md)** - Canonical reference for all utility functions
1373
320
 
1374
- ### Core Features
321
+ ### Examples
1375
322
 
1376
- - **Unidirectional Data Flow** - Predictable state management with Angular signals
1377
- - **Type Safety** - Full TypeScript support with `NgxDeepPartial<T>` and `NgxDeepRequired<T>`
1378
- - **Enhanced Field Path Types** - Template literal autocomplete for field names ([Documentation](./docs/FIELD-PATHS.md))
1379
- - **Zero Boilerplate** - Automatic FormControl and FormGroup creation
1380
- - **Shape Validation** - Runtime validation against your TypeScript models (dev mode)
323
+ - **[Examples Project](./projects/examples)** - Working code examples with business hours forms, purchase forms, and validation config demos
324
+ - Run locally: `npm install && npm start`
325
+ - Includes smart components, UI components, and complete validation patterns
1381
326
 
1382
- ### Advanced Validation
327
+ ## Migration
1383
328
 
1384
- - **Async Validations** - Built-in support with AbortController and pending state
1385
- - **Conditional Logic** - Use `omitWhen()` for conditional validation rules
1386
- - **Composable Suites** - Reusable validation functions across projects
1387
- - **Custom Debouncing** - Configure validation timing per field or form
1388
- - **Warnings Support** - Non-blocking feedback with Vest's `warn()` feature
1389
- - **Performance Optimization** - Field-level validation with `only()` pattern
329
+ - v1.x → v2.0.0: **[Migration Guide](./docs/migration/MIGRATION-v1.x-to-v2.0.0.md)**
330
+ - Selector prefixes: **[Dual Selector Support](./docs/DUAL-SELECTOR-SUPPORT.md)**
1390
331
 
1391
- ### Dynamic Forms
332
+ Browser support follows Angular 19+ targets (no `structuredClone` polyfill required).
1392
333
 
1393
- - **Conditional Fields** - Show/hide fields based on form state
1394
- - **Form Arrays** - Dynamic lists with add/remove functionality
1395
- - **Reactive Disabling** - Disable fields based on computed signals
1396
- - **State Management** - Preserve field state across conditional rendering
1397
- - **Structure Change Detection** - Manual trigger for validation updates when form structure changes
334
+ ## FAQ
1398
335
 
1399
- ### Developer Experience
336
+ ### Do I need validations to use ngx-vest-forms?
1400
337
 
1401
- - **Runtime Shape Checking** - Catch typos in `name` attributes early
1402
- - **Flexible Error Display** - Built-in `ngx-control-wrapper` or create custom wrappers with `FormErrorDisplayDirective`
1403
- - **Error Display Modes** - Control when errors show: on-blur, on-submit, or both
1404
- - **Validation Config** - Declare field dependencies for complex scenarios
1405
- - **Field State Utilities** - Helper functions for managing dynamic form state
1406
- - **Modern Angular** - Built for Angular 18+ with standalone components and signals
338
+ No—but you’ll almost always want them. Common cases to start without a suite:
1407
339
 
1408
- ## Documentation
340
+ - Prototyping UI while deferring rules
341
+ - Gradual migration: adopt unidirectional state and type-safe models first
342
+ - Server-driven validation: display backend errors while you add a client suite later
1409
343
 
1410
- ### Detailed Guides
1411
-
1412
- For comprehensive documentation beyond this README, check out our detailed guides:
1413
-
1414
- - **[Accessibility Guide](./docs/ACCESSIBILITY.md)** - WCAG 2.2 AA compliance and best practices
1415
- - Polite vs assertive ARIA live regions
1416
- - Field-level and form-level error strategies
1417
- - Implementation details and testing guidance
1418
- - **[Field Path Types](./docs/FIELD-PATHS.md)** - Type-safe field references with IDE autocomplete
1419
- - Template literal types for field paths
1420
- - `ValidationConfigMap<T>` for type-safe configs
1421
- - `FormFieldName<T>` for Vest suites
1422
- - Migration guide and best practices
1423
- - Performance considerations
1424
- - **[Utility Types & Functions Reference](./projects/ngx-vest-forms/src/lib/utils/README.md)** - Complete guide to all public utility types and functions
1425
- - Type utilities: `NgxDeepPartial`, `NgxDeepRequired`, `NgxFormCompatibleDeepRequired`
1426
- - Form utilities: `setValueAtPath()`, `createEmptyFormState()`
1427
- - Array/Object conversion: `arrayToObject()`, `deepArrayToObject()`, `objectToArray()`
1428
- - Field path utilities: `stringifyFieldPath()`
1429
- - Field clearing: `clearFieldsWhen()`, `clearFields()`, `keepFieldsWhen()`
1430
- - Validation config builder: `createValidationConfig()`
1431
- - **[Structure Change Detection Guide](./docs/STRUCTURE_CHANGE_DETECTION.md)** - Advanced handling of conditional form scenarios
1432
- - Alternative approaches and their trade-offs
1433
- - Performance considerations and best practices
1434
- - Detailed API reference with examples
1435
- - When and why to use `triggerFormValidation()`
1436
-
1437
- ### Coming Soon
1438
-
1439
- - **Advanced Form Arrays Guide** - Dynamic lists, nested arrays, and complex scenarios
1440
- - **Custom Validation Guide** - Building reusable validation suites and complex rules
1441
- - **Performance Optimization Guide** - Tips and techniques for large-scale forms
344
+ You can add a Vest suite at any time by binding `[suite]` on the form.
1442
345
 
1443
346
  ## Resources
1444
347
 
@@ -1447,12 +350,9 @@ For comprehensive documentation beyond this README, check out our detailed guide
1447
350
  - **[Angular Official Documentation](https://angular.dev/guide/forms)** - Template-driven forms guide
1448
351
  - **[Vest.js Documentation](https://vestjs.dev)** - Validation framework used by ngx-vest-forms
1449
352
  - **[Live Examples Repository](https://github.com/ngx-vest-forms/ngx-vest-forms/tree/master/projects/examples)** - Complex form examples and patterns
1450
- - **[Interactive Stackblitz Demo](https://stackblitz.com/~/github.com/simplifiedcourses/ngx-vest-forms-stackblitz)** - Try it in your browser
1451
353
 
1452
354
  ### Running Examples Locally
1453
355
 
1454
- Clone this repo and run the examples:
1455
-
1456
356
  ```bash
1457
357
  npm install
1458
358
  npm start
@@ -1460,8 +360,6 @@ npm start
1460
360
 
1461
361
  ### Learning Resources
1462
362
 
1463
- [![Angular Forms Course](course.jpeg)](https://www.simplified.courses/complex-angular-template-driven-forms)
1464
-
1465
363
  **[Complex Angular Template-Driven Forms Course](https://www.simplified.courses/complex-angular-template-driven-forms)** - Master advanced form patterns and become a form expert.
1466
364
 
1467
365
  ### Founding Articles by Brecht Billiet
@@ -1473,12 +371,6 @@ This library was originally created by [Brecht Billiet](https://twitter.com/brec
1473
371
  - **[Asynchronous Form Validators in Angular with Vest](https://blog.simplified.courses/asynchronous-form-validators-in-angular-with-vest/)** - Advanced async validation patterns
1474
372
  - **[Template-Driven Forms with Form Arrays](https://blog.simplified.courses/template-driven-forms-with-form-arrays/)** - Dynamic form arrays implementation
1475
373
 
1476
- ### Community & Support
1477
-
1478
- - **[GitHub Issues](https://github.com/ngx-vest-forms/ngx-vest-forms/issues)** - Report bugs or request features
1479
- - **[GitHub Discussions](https://github.com/ngx-vest-forms/ngx-vest-forms/discussions)** - Ask questions and share ideas
1480
- - **[npm Package](https://www.npmjs.com/package/ngx-vest-forms)** - Official package page
1481
-
1482
374
  ## Developer Resources
1483
375
 
1484
376
  ### Comprehensive Instruction Files
@@ -1489,33 +381,6 @@ This project includes detailed instruction files designed to help developers mas
1489
381
  - **[`.github/instructions/vest.instructions.md`](.github/instructions/vest.instructions.md)** - Comprehensive Vest.js validation patterns and best practices
1490
382
  - **[`.github/copilot-instructions.md`](.github/copilot-instructions.md)** - Main GitHub Copilot instructions for this workspace
1491
383
 
1492
- ### Using Instruction Files in Your Workspace
1493
-
1494
- For the best development experience with ngx-vest-forms, **copy these instruction files to your own project's `.github/` directory**:
1495
-
1496
- ```bash
1497
- # Create the directories in your project
1498
- mkdir -p .github/instructions
1499
-
1500
- # Copy the instruction files
1501
- curl -o .github/instructions/ngx-vest-forms.instructions.md \
1502
- https://raw.githubusercontent.com/ngx-vest-forms/ngx-vest-forms/main/.github/instructions/ngx-vest-forms.instructions.md
1503
-
1504
- curl -o .github/instructions/vest.instructions.md \
1505
- https://raw.githubusercontent.com/ngx-vest-forms/ngx-vest-forms/main/.github/instructions/vest.instructions.md
1506
-
1507
- # Optionally, adapt the main copilot instructions for your project
1508
- curl -o .github/copilot-instructions.md \
1509
- https://raw.githubusercontent.com/ngx-vest-forms/ngx-vest-forms/main/.github/copilot-instructions.md
1510
- ```
1511
-
1512
- **Benefits of copying instruction files:**
1513
-
1514
- - **GitHub Copilot Integration** - Enhanced code generation aligned with best practices
1515
- - **Comprehensive Documentation** - Complete patterns and examples at your fingertips
1516
- - **Consistent Code Quality** - Maintain validation patterns and architectural standards
1517
- - **Faster Development** - Quick reference for complex scenarios and optimizations
1518
-
1519
384
  ## Acknowledgments
1520
385
 
1521
386
  🙏 **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.