ngx-vest-forms 1.1.0 → 1.2.0

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.
Files changed (34) hide show
  1. package/README.md +688 -192
  2. package/fesm2022/ngx-vest-forms.mjs +141 -78
  3. package/fesm2022/ngx-vest-forms.mjs.map +1 -1
  4. package/index.d.ts +261 -3
  5. package/package.json +8 -7
  6. package/esm2022/lib/components/control-wrapper/control-wrapper.component.mjs +0 -60
  7. package/esm2022/lib/constants.mjs +0 -2
  8. package/esm2022/lib/directives/form-model-group.directive.mjs +0 -43
  9. package/esm2022/lib/directives/form-model.directive.mjs +0 -43
  10. package/esm2022/lib/directives/form.directive.mjs +0 -175
  11. package/esm2022/lib/directives/validate-root-form.directive.mjs +0 -85
  12. package/esm2022/lib/directives/validation-options.mjs +0 -2
  13. package/esm2022/lib/exports.mjs +0 -70
  14. package/esm2022/lib/utils/array-to-object.mjs +0 -4
  15. package/esm2022/lib/utils/deep-partial.mjs +0 -2
  16. package/esm2022/lib/utils/deep-required.mjs +0 -2
  17. package/esm2022/lib/utils/form-utils.mjs +0 -180
  18. package/esm2022/lib/utils/shape-validation.mjs +0 -61
  19. package/esm2022/ngx-vest-forms.mjs +0 -5
  20. package/esm2022/public-api.mjs +0 -14
  21. package/lib/components/control-wrapper/control-wrapper.component.d.ts +0 -18
  22. package/lib/constants.d.ts +0 -1
  23. package/lib/directives/form-model-group.directive.d.ts +0 -15
  24. package/lib/directives/form-model.directive.d.ts +0 -15
  25. package/lib/directives/form.directive.d.ts +0 -88
  26. package/lib/directives/validate-root-form.directive.d.ts +0 -26
  27. package/lib/directives/validation-options.d.ts +0 -9
  28. package/lib/exports.d.ts +0 -17
  29. package/lib/utils/array-to-object.d.ts +0 -3
  30. package/lib/utils/deep-partial.d.ts +0 -8
  31. package/lib/utils/deep-required.d.ts +0 -7
  32. package/lib/utils/form-utils.d.ts +0 -36
  33. package/lib/utils/shape-validation.d.ts +0 -15
  34. package/public-api.d.ts +0 -13
package/README.md CHANGED
@@ -1,34 +1,95 @@
1
+ <!-- prettier-ignore -->
2
+ <div align="center">
3
+
4
+ <img src="./course.jpeg" alt="ngx-vest-forms" align="center" height="96" />
5
+
1
6
  # ngx-vest-forms
2
7
 
3
- ### Introduction
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
+ [![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)
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-blue?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
12
+ [![License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE)
4
13
 
5
- This is a very lightweight adapter for Angular template-driven forms and [vestjs](https://vestjs.dev).
6
- This package gives us the ability to create unidirectional forms without any boilerplate.
7
- It is meant for complex forms with a high focus on complex validations and conditionals.
14
+ If you like this project, star it on GitHub it helps a lot!
8
15
 
9
- All the validations are asynchronous and use [vestjs](https://vestjs.dev) suites that can be re-used
10
- across different frameworks and technologies.
16
+ [Overview](#overview) [Getting Started](#getting-started) [Features](#features) [Basic Usage](#basic-usage) [Examples](#examples) [Resources](#resources) • [Developer Resources](#developer-resources) • [Acknowledgments](#acknowledgments)
11
17
 
12
- ### Installation
18
+ </div>
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
+ 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.
24
+
25
+ > [!TIP]
26
+ > **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.
27
+
28
+ ## Overview
29
+
30
+ **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:
31
+
32
+ - **Unidirectional Data Flow** - Predictable state management with Angular signals
33
+ - **Type Safety** - Full TypeScript support with runtime shape validation
34
+ - **Async Validations** - Built-in support for complex, conditional validations
35
+ - **Zero Boilerplate** - Automatic form control creation and validation wiring
36
+ - **Conditional Logic** - Show/hide fields and validation rules dynamically
37
+ - **Reusable Validations** - Share validation suites across frameworks
38
+
39
+ ### Why Choose ngx-vest-forms?
40
+
41
+ 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.
42
+
43
+ ```typescript
44
+ // Before: Complex reactive form setup
45
+ const form = this.fb.group({
46
+ generalInfo: this.fb.group({
47
+ firstName: ['', [Validators.required]],
48
+ lastName: ['', [Validators.required]]
49
+ })
50
+ });
51
+
52
+ // After: Simple, type-safe template-driven approach
53
+ protected readonly formValue = signal<MyFormModel>({});
54
+ protected readonly suite = myValidationSuite;
55
+ ```
13
56
 
14
- You can install the package by running:
57
+ ## Getting Started
15
58
 
16
- ```shell
17
- npm i ngx-vest-forms
59
+ ### Prerequisites
60
+
61
+ - **Angular**: >=18.0.0 (Signals support required)
62
+ - **Vest.js**: >=5.4.6 (Validation engine)
63
+ - **TypeScript**: >=5.8.0 (Modern Angular features)
64
+ - **Node.js**: >=22.0.0 (Required for Angular 18+)
65
+
66
+ ### Installation
67
+
68
+ ```bash
69
+ npm install ngx-vest-forms
18
70
  ```
19
71
 
20
- ### Creating a simple form
72
+ ### Quick Start
21
73
 
22
- Let's start by explaining how to create a simple form.
23
- I want a form with a form group called `general` info that has 2 properties:
24
- - `firstName`
25
- - `lastName`
74
+ Create your first ngx-vest-forms component in 3 simple steps:
26
75
 
27
- We need to import the `vestForms` const in the imports section of the `@Component` decorator.
28
- Now we can apply the `scVestForm` directive to the `form` tag and listen to the `formValueChange` output to feed our signal.
29
- In the form we create a form group for `generalInfo` with the `ngModelGroup` directive.
30
- And we crate 2 inputs with the `name` attribute and the `[ngModel]` input.
31
- **Do note that we are not using the banana in the box syntax but only tha square brackets, resulting in a unidirectional dataflow**
76
+ #### Step 1: Define your form model
77
+
78
+ ```typescript
79
+ import { signal } from '@angular/core';
80
+ import { vestForms, DeepPartial } from 'ngx-vest-forms';
81
+
82
+ type MyFormModel = DeepPartial<{
83
+ generalInfo: {
84
+ firstName: string;
85
+ lastName: string;
86
+ };
87
+ }>;
88
+ ```
89
+
90
+ #### Step 2: Set up your component
91
+
92
+ Use `[ngModel]` (not `[(ngModel)]`) for unidirectional data flow:
32
93
 
33
94
  ```typescript
34
95
  import { vestForms, DeepPartial } from 'ngx-vest-forms';
@@ -38,24 +99,34 @@ type MyFormModel = DeepPartial<{
38
99
  generalInfo: {
39
100
  firstName: string;
40
101
  lastName: string;
41
- }
42
- }>
102
+ };
103
+ }>;
43
104
 
44
105
  @Component({
45
106
  imports: [vestForms],
46
107
  template: `
47
- <form scVestForm
108
+ <form
109
+ scVestForm
48
110
  (formValueChange)="formValue.set($event)"
49
- (ngSubmit)="onSubmit()">
111
+ (ngSubmit)="onSubmit()"
112
+ >
50
113
  <div ngModelGroup="generalInfo">
51
114
  <label>First name</label>
52
- <input type="text" name="firstName" [ngModel]="formValue().generalInformation?.firstName"/>
53
-
115
+ <input
116
+ type="text"
117
+ name="firstName"
118
+ [ngModel]="formValue().generalInfo?.firstName"
119
+ />
120
+
54
121
  <label>Last name</label>
55
- <input type="text" name="lastName" [ngModel]="formValue().generalInformation?.lastName"/>
122
+ <input
123
+ type="text"
124
+ name="lastName"
125
+ [ngModel]="formValue().generalInfo?.lastName"
126
+ />
56
127
  </div>
57
- </form>
58
- `
128
+ </form>
129
+ `,
59
130
  })
60
131
  export class MyComponent {
61
132
  // This signal will hold the state of our form
@@ -63,22 +134,59 @@ export class MyComponent {
63
134
  }
64
135
  ```
65
136
 
66
- **Note: Template-driven forms are deep partial, so always use the `?` operator in your templates.**
137
+ #### Step 3: That's it! 🎉
138
+
139
+ Your form automatically creates FormGroups and FormControls with type-safe, unidirectional data flow.
140
+
141
+ > [!IMPORTANT]
142
+ > Notice we use `[ngModel]` (not `[(ngModel)]`) for unidirectional data flow, and the `?` operator since template-driven forms are `DeepPartial`.
143
+
144
+ ## Features
145
+
146
+ ### Core Features
147
+
148
+ - **Unidirectional Data Flow** - Predictable state management with Angular signals
149
+ - **Type Safety** - Full TypeScript support with `DeepPartial<T>` and `DeepRequired<T>`
150
+ - **Zero Boilerplate** - Automatic FormControl and FormGroup creation
151
+ - **Shape Validation** - Runtime validation against your TypeScript models (dev mode)
152
+
153
+ ### Advanced Validation
154
+
155
+ - **Async Validations** - Built-in support with AbortController
156
+ - **Conditional Logic** - Use `omitWhen()` for conditional validation rules
157
+ - **Composable Suites** - Reusable validation functions across projects
158
+ - **Custom Debouncing** - Configure validation timing per field or form
159
+
160
+ ### Dynamic Forms
67
161
 
68
- That's it! This will feed the `formValue` signal and angular will create a form group and 2 form controls for us automatically.
69
- The object that will be fed in the `formValue` signal will look like this:
162
+ - **Conditional Fields** - Show/hide fields based on form state
163
+ - **Form Arrays** - Dynamic lists with add/remove functionality
164
+ - **Reactive Disabling** - Disable fields based on computed signals
165
+ - **State Management** - Preserve field state across conditional rendering
166
+
167
+ ### Developer Experience
168
+
169
+ - **Runtime Shape Checking** - Catch typos in `name` attributes early
170
+ - **Built-in Error Display** - `sc-control-wrapper` component for consistent UX
171
+ - **Validation Config** - Declare field dependencies for complex scenarios
172
+ - **Modern Angular** - Built for Angular 18+ with standalone components
173
+
174
+ ## Basic Usage
175
+
176
+ The form value will be automatically populated like this:
70
177
 
71
178
  ```typescript
72
179
  formValue = {
73
180
  generalInfo: {
74
181
  firstName: '',
75
- lastName: ''
76
- }
77
- }
182
+ lastName: '',
183
+ },
184
+ };
78
185
  ```
79
186
 
80
187
  The ngForm will contain automatically created FormGroups and FormControls.
81
188
  This does not have anything to do with this package. It's just Angular:
189
+
82
190
  ```typescript
83
191
  form = {
84
192
  controls: {
@@ -92,14 +200,14 @@ form = {
92
200
  }
93
201
  ```
94
202
 
95
- The `scVestForm` directive offers some basic outputs for us though:
203
+ The `scVestForm` directive offers these outputs:
96
204
 
97
- | Output | Description |
98
- |----------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
99
- | formValueChange<T> | Emits when the form value changes. But debounces<br/> the events since template-driven forms are created by the<br/>framework over time |
100
- | dirtyChange<boolean> | Emits when the dirty state of the form changes |
101
- | validChange<boolean> | Emits when the form becomes dirty or pristine |
102
- | errorsChange | Emits an entire list of the form and all its form groups and controls |
205
+ | Output | Description |
206
+ | ----------------- | ----------------------------------------------------------------------------------------------- |
207
+ | `formValueChange` | Emits when the form value changes (debounced since template-driven forms are created over time) |
208
+ | `dirtyChange` | Emits when the dirty state of the form changes |
209
+ | `validChange` | Emits when the form becomes valid or invalid |
210
+ | `errorsChange` | Emits the complete list of errors for the form and all its controls |
103
211
 
104
212
  ### Avoiding typo's
105
213
 
@@ -114,33 +222,42 @@ type MyFormModel = DeepPartial<{
114
222
  generalInfo: {
115
223
  firstName: string;
116
224
  lastName: string;
117
- }
118
- }>
225
+ };
226
+ }>;
119
227
 
120
228
  export const myFormModelShape: DeepRequired<MyFormModel> = {
121
229
  generalInfo: {
122
230
  firstName: '',
123
- lastName: ''
124
- }
231
+ lastName: '',
232
+ },
125
233
  };
126
234
 
127
235
  @Component({
128
236
  imports: [vestForms],
129
237
  template: `
130
- <form scVestForm
238
+ <form
239
+ scVestForm
131
240
  [formShape]="shape"
132
241
  (formValueChange)="formValue.set($event)"
133
- (ngSubmit)="onSubmit()">
134
-
242
+ (ngSubmit)="onSubmit()"
243
+ >
135
244
  <div ngModelGroup="generalInfo">
136
245
  <label>First name</label>
137
- <input type="text" name="firstName" [ngModel]="formValue().generalInformation?.firstName"/>
138
-
246
+ <input
247
+ type="text"
248
+ name="firstName"
249
+ [ngModel]="formValue().generalInformation?.firstName"
250
+ />
251
+
139
252
  <label>Last name</label>
140
- <input type="text" name="lastName" [ngModel]="formValue().generalInformation?.lastName"/>
253
+ <input
254
+ type="text"
255
+ name="lastName"
256
+ [ngModel]="formValue().generalInformation?.lastName"
257
+ />
141
258
  </div>
142
- </form>
143
- `
259
+ </form>
260
+ `,
144
261
  })
145
262
  export class MyComponent {
146
263
  protected readonly formValue = signal<MyFormModel>({});
@@ -179,7 +296,7 @@ Error: Shape mismatch:
179
296
 
180
297
  ### Conditional fields
181
298
 
182
- What if we want to remove a form control or form group? With reactive forms that would require a lot of work
299
+ What if we want to remove a form control or form group? With reactive forms that would require a lot of work
183
300
  but since Template driven forms do all the hard work for us, we can simply create a computed signal for that and
184
301
  bind that in the template. Having logic in the template is considered a bad practice, so we can do all
185
302
  the calculations in our class.
@@ -189,11 +306,19 @@ Let's hide `lastName` if `firstName` is not filled in:
189
306
  ```html
190
307
  <div ngModelGroup="generalInfo">
191
308
  <label>First name</label>
192
- <input type="text" name="firstName" [ngModel]="formValue().generalInformation?.firstName"/>
193
-
309
+ <input
310
+ type="text"
311
+ name="firstName"
312
+ [ngModel]="formValue().generalInformation?.firstName"
313
+ />
314
+
194
315
  @if(lastNameAvailable()){
195
- <label>Last name</label>
196
- <input type="text" name="lastName" [ngModel]="formValue().generalInformation?.lastName"/>
316
+ <label>Last name</label>
317
+ <input
318
+ type="text"
319
+ name="lastName"
320
+ [ngModel]="formValue().generalInformation?.lastName"
321
+ />
197
322
  }
198
323
  </div>
199
324
  ```
@@ -201,8 +326,8 @@ Let's hide `lastName` if `firstName` is not filled in:
201
326
  ```typescript
202
327
  class MyComponent {
203
328
  ...
204
- protected readonly lastNameAvailable =
205
- computed(() => !!this.formValue().generalInformation?.firstName);
329
+ protected readonly lastNameAvailable =
330
+ computed(() => !!this.formValue().generalInfo?.firstName);
206
331
  }
207
332
  ```
208
333
 
@@ -211,13 +336,21 @@ This also works for a form group:
211
336
 
212
337
  ```html
213
338
  @if(showGeneralInfo()){
214
- <div ngModelGroup="generalInfo">
215
- <label>First name</label>
216
- <input type="text" name="firstName" [ngModel]="formValue().generalInformation?.firstName"/>
217
-
218
- <label>Last name</label>
219
- <input type="text" name="lastName" [ngModel]="formValue().generalInformation?.lastName"/>
220
- </div>
339
+ <div ngModelGroup="generalInfo">
340
+ <label>First name</label>
341
+ <input
342
+ type="text"
343
+ name="firstName"
344
+ [ngModel]="formValue().generalInformation?.firstName"
345
+ />
346
+
347
+ <label>Last name</label>
348
+ <input
349
+ type="text"
350
+ name="lastName"
351
+ [ngModel]="formValue().generalInformation?.lastName"
352
+ />
353
+ </div>
221
354
  }
222
355
  ```
223
356
 
@@ -227,18 +360,150 @@ To achieve reactive disabling, we just have to take advantage of computed signal
227
360
 
228
361
  ```typescript
229
362
  class MyComponent {
230
- protected readonly lastNameDisabled =
231
- computed(() => !this.formValue().generalInformation?.firstName);
363
+ protected readonly lastNameDisabled = computed(
364
+ () => !this.formValue().generalInfo?.firstName
365
+ );
232
366
  }
233
367
  ```
234
368
 
235
369
  We can bind the computed signal to the `disabled` directive of Angular.
370
+
236
371
  ```html
237
- <input type="text" name="lastName"
238
- [disabled]="lastNameDisabled()"
239
- [ngModel]="formValue().generalInformation?.lastName"/>
372
+ <input
373
+ type="text"
374
+ name="lastName"
375
+ [disabled]="lastNameDisabled()"
376
+ [ngModel]="formValue().generalInformation?.lastName"
377
+ />
378
+ ```
379
+
380
+ ## Examples
381
+
382
+ ### Simple Form with Validation
383
+
384
+ Here's a complete example showing form setup, validation, and error display:
385
+
386
+ ```typescript
387
+ import { Component, signal } from '@angular/core';
388
+ import { staticSuite, test, enforce } from 'vest';
389
+ import { vestForms, DeepPartial, DeepRequired } from 'ngx-vest-forms';
390
+
391
+ // 1. Define your form model
392
+ type UserFormModel = DeepPartial<{
393
+ firstName: string;
394
+ lastName: string;
395
+ email: string;
396
+ }>;
397
+
398
+ // 2. Create a shape for runtime validation (recommended)
399
+ const userFormShape: DeepRequired<UserFormModel> = {
400
+ firstName: '',
401
+ lastName: '',
402
+ email: '',
403
+ };
404
+
405
+ // 3. Create a Vest validation suite
406
+ const userValidationSuite = staticSuite(
407
+ (model: UserFormModel, field?: string) => {
408
+ if (field) {
409
+ only(field); // Critical for performance - only validate the active field
410
+ }
411
+
412
+ test('firstName', 'First name is required', () => {
413
+ enforce(model.firstName).isNotBlank();
414
+ });
415
+
416
+ test('lastName', 'Last name is required', () => {
417
+ enforce(model.lastName).isNotBlank();
418
+ });
419
+
420
+ test('email', 'Valid email is required', () => {
421
+ enforce(model.email).isEmail();
422
+ });
423
+ }
424
+ );
425
+
426
+ @Component({
427
+ selector: 'app-user-form',
428
+ imports: [vestForms],
429
+ template: `
430
+ <form
431
+ scVestForm
432
+ [suite]="suite"
433
+ [formShape]="shape"
434
+ (formValueChange)="formValue.set($event)"
435
+ (ngSubmit)="onSubmit()"
436
+ >
437
+ <div sc-control-wrapper>
438
+ <label>First Name</label>
439
+ <input [ngModel]="formValue().firstName" name="firstName" />
440
+ </div>
441
+
442
+ <div sc-control-wrapper>
443
+ <label>Last Name</label>
444
+ <input [ngModel]="formValue().lastName" name="lastName" />
445
+ </div>
446
+
447
+ <div sc-control-wrapper>
448
+ <label>Email</label>
449
+ <input [ngModel]="formValue().email" name="email" type="email" />
450
+ </div>
451
+
452
+ <button type="submit">Submit</button>
453
+ </form>
454
+ `,
455
+ })
456
+ export class UserFormComponent {
457
+ protected readonly formValue = signal<UserFormModel>({});
458
+ protected readonly suite = userValidationSuite;
459
+ protected readonly shape = userFormShape;
460
+
461
+ protected onSubmit() {
462
+ console.log('Form submitted:', this.formValue());
463
+ }
464
+ }
240
465
  ```
241
466
 
467
+ ### Conditional Fields Example
468
+
469
+ ```typescript
470
+ @Component({
471
+ template: `
472
+ <form scVestForm [suite]="suite" (formValueChange)="formValue.set($event)">
473
+ <!-- Age field -->
474
+ <div sc-control-wrapper>
475
+ <label>Age</label>
476
+ <input [ngModel]="formValue().age" name="age" type="number" />
477
+ </div>
478
+
479
+ <!-- Emergency contact - only required if under 18 -->
480
+ @if (emergencyContactRequired()) {
481
+ <div sc-control-wrapper>
482
+ <label>Emergency Contact</label>
483
+ <input
484
+ [ngModel]="formValue().emergencyContact"
485
+ name="emergencyContact"
486
+ />
487
+ </div>
488
+ }
489
+ </form>
490
+ `,
491
+ })
492
+ export class ConditionalFormComponent {
493
+ protected readonly formValue = signal<ConditionalFormModel>({});
494
+
495
+ // Computed signal for conditional logic
496
+ protected readonly emergencyContactRequired = computed(
497
+ () => (this.formValue().age || 0) < 18
498
+ );
499
+ }
500
+ ```
501
+
502
+ ### Live Examples
503
+
504
+ - **[Purchase Form Demo](https://github.com/ngx-vest-forms/ngx-vest-forms/tree/master/projects/examples/src/app/components/smart/purchase-form)** - Complex form with nested objects, validation dependencies, and conditional logic
505
+ - **[Business Hours Demo](https://github.com/ngx-vest-forms/ngx-vest-forms/tree/master/projects/examples/src/app/components/smart/business-hours-form)** - Dynamic form arrays with complex validation rules
506
+
242
507
  ### Validations
243
508
 
244
509
  The absolute gem in ngx-vest-forms is the flexibility in validations without writing any boilerplate.
@@ -247,13 +512,50 @@ You can use it on the backend/frontend/Angular/react etc...
247
512
 
248
513
  We use vest because it introduces the concept of vest suites. These are suites that kind of look like unit-tests
249
514
  but that are highly flexible:
250
- * [X] Write validations on forms
251
- * [X] Write validations on form groups
252
- * [X] Write validations on form controls
253
- * [X] Composable/reuse-able different validation suites
254
- * [X] Write conditional validations
515
+
516
+ - [x] Write validations on forms
517
+ - [x] Write validations on form groups
518
+ - [x] Write validations on form controls
519
+ - [x] Composable/reuse-able different validation suites
520
+ - [x] Write conditional validations
521
+
522
+ ### Validation Performance with `only()`
523
+
524
+ ngx-vest-forms automatically optimizes validation performance by running validations only for the field being interacted with. This is achieved through Vest's `only()` function:
525
+
526
+ ```typescript
527
+ import { enforce, only, staticSuite, test } from 'vest';
528
+
529
+ export const myFormModelSuite = staticSuite(
530
+ (model: MyFormModel, field?: string) => {
531
+ if (field) {
532
+ only(field); // Only validate the specific field during user interaction
533
+ }
534
+ // When field is undefined (e.g., on submit), all validations run
535
+
536
+ test('firstName', 'First name is required', () => {
537
+ enforce(model.firstName).isNotBlank();
538
+ });
539
+ test('lastName', 'Last name is required', () => {
540
+ enforce(model.lastName).isNotBlank();
541
+ });
542
+ }
543
+ );
544
+ ```
545
+
546
+ This pattern ensures:
547
+
548
+ - ✅ During typing/blur: Only the current field validates (better performance)
549
+ - ✅ On form submit: All fields validate (complete validation)
550
+ - ✅ Untouched fields don't show errors prematurely (better UX)
551
+
552
+ > [!IMPORTANT]
553
+ > Always include the optional `field?: string` parameter in your suite and use the `only(field)` pattern. The library automatically passes the field name during individual field validation.
554
+
555
+ ### Basic Validation Suite
255
556
 
256
557
  This is how you write a simple Vest suite:
558
+
257
559
  ```typescript
258
560
  import { enforce, only, staticSuite, test } from 'vest';
259
561
  import { MyFormModel } from '../models/my-form.model'
@@ -291,21 +593,23 @@ class MyComponent {
291
593
  ```
292
594
 
293
595
  ```html
294
-
295
- <form scVestForm
296
- [formShape]="shape"
297
- [formValue]="formValue"
298
- [suite]="suite"
299
- (formValueChange)="formValue.set($event)"
300
- (ngSubmit)="onSubmit()">
596
+ <form
597
+ scVestForm
598
+ [formShape]="shape"
599
+ [formValue]="formValue"
600
+ [suite]="suite"
601
+ (formValueChange)="formValue.set($event)"
602
+ (ngSubmit)="onSubmit()"
603
+ >
301
604
  ...
302
605
  </form>
303
606
  ```
304
607
 
305
- That's it. Validations are completely wired now. Because ngx-vest-forms will hook into the
608
+ That's it. Validations are completely wired now. Because ngx-vest-forms will hook into the
306
609
  `[ngModel]` and `ngModelGroup` attributes, and create ngValidators automatically.
307
610
 
308
- It goes like this:
611
+ It goes like this:
612
+
309
613
  - Control gets created, Angular recognizes the `ngModel` and `ngModelGroup` directives
310
614
  - These directives implement `AsyncValidator` and will connect to a vest suite
311
615
  - User types into control
@@ -314,7 +618,7 @@ It goes like this:
314
618
  - Vest returns the errors
315
619
  - @simpilfied/forms puts those errors on the angular form control
316
620
 
317
- This means that `valid`, `invalid`, `errors`, `statusChanges` etc will keep on working
621
+ This means that `valid`, `invalid`, `errors`, `statusChanges` etc will keep on working
318
622
  just like it would with a regular angular form.
319
623
 
320
624
  #### Showing validation errors
@@ -323,10 +627,12 @@ Now we want to show the validation errors in a consistent way.
323
627
  For that we have provided the `sc-control-wrapper` attribute component.
324
628
 
325
629
  You can use it on:
630
+
326
631
  - elements that hold `ngModelGroup`
327
632
  - elements that have an `ngModel` (or form control) inside of them.
328
633
 
329
634
  This will show errors automatically on:
635
+
330
636
  - form submit
331
637
  - blur
332
638
 
@@ -351,12 +657,13 @@ Let's update our form:
351
657
  ```
352
658
 
353
659
  This is the only thing we need to do to create a form that is completely wired with vest.
354
- * [x] Automatic creation of form controls and form groups
355
- * [x] Automatic connection to vest suites
356
- * [x] Automatic typo validation
357
- * [x] Automatic adding of css error classes and showing validation messages
358
- * [x] On blur
359
- * [x] On submit
660
+
661
+ - [x] Automatic creation of form controls and form groups
662
+ - [x] Automatic connection to vest suites
663
+ - [x] Automatic typo validation
664
+ - [x] Automatic adding of css error classes and showing validation messages
665
+ - [x] On blur
666
+ - [x] On submit
360
667
 
361
668
  ### Conditional validations
362
669
 
@@ -381,9 +688,9 @@ omitWhen((model.age || 0) >= 18, () => {
381
688
  You can put those validations on every field that you want. On form group fields and on form control fields.
382
689
  Check this interesting example below:
383
690
 
384
- * [x] Password is always required
385
- * [x] Confirm password is only required when there is a password
386
- * [x] The passwords should match, but only when they are both filled in
691
+ - [x] Password is always required
692
+ - [x] Confirm password is only required when there is a password
693
+ - [x] The passwords should match, but only when they are both filled in
387
694
 
388
695
  ```typescript
389
696
  test('passwords.password', 'Password is not filled in', () => {
@@ -394,11 +701,16 @@ omitWhen(!model.passwords?.password, () => {
394
701
  enforce(model.passwords?.confirmPassword).isNotBlank();
395
702
  });
396
703
  });
397
- omitWhen(!model.passwords?.password || !model.passwords?.confirmPassword, () => {
398
- test('passwords', 'Passwords do not match', () => {
399
- enforce(model.passwords?.confirmPassword).equals(model.passwords?.password);
400
- });
401
- });
704
+ omitWhen(
705
+ !model.passwords?.password || !model.passwords?.confirmPassword,
706
+ () => {
707
+ test('passwords', 'Passwords do not match', () => {
708
+ enforce(model.passwords?.confirmPassword).equals(
709
+ model.passwords?.password
710
+ );
711
+ });
712
+ }
713
+ );
402
714
  ```
403
715
 
404
716
  Forget about manually adding, removing validators on reactive forms and not being able to
@@ -414,7 +726,10 @@ This is quite straightforward with Vest.
414
726
  Let's take this simple function that validates an address:
415
727
 
416
728
  ```typescript
417
- export function addressValidations(model: AddressModel | undefined, field: string): void {
729
+ export function addressValidations(
730
+ model: AddressModel | undefined,
731
+ field: string
732
+ ): void {
418
733
  test(`${field}.street`, 'Street is required', () => {
419
734
  enforce(model?.street).isNotBlank();
420
735
  });
@@ -444,8 +759,14 @@ export const mySuite = staticSuite(
444
759
  if (field) {
445
760
  only(field);
446
761
  }
447
- addressValidations(model.addresses?.billingAddress, 'addresses.billingAddress');
448
- addressValidations(model.addresses?.shippingAddress, 'addresses.shippingAddress');
762
+ addressValidations(
763
+ model.addresses?.billingAddress,
764
+ 'addresses.billingAddress'
765
+ );
766
+ addressValidations(
767
+ model.addresses?.shippingAddress,
768
+ 'addresses.shippingAddress'
769
+ );
449
770
  }
450
771
  );
451
772
  ```
@@ -460,59 +781,54 @@ Checkbox is checked. But if it is checked, all fields are required.
460
781
  And if both addresses are filled in, they should be different.
461
782
 
462
783
  This gives us validation on:
463
- * [x] The addresses form field (they can't be equal)
464
- * [x] The shipping Address field (only required when checkbox is checked)
465
- * [x] validation on all the address fields (street, number, etc) on both addresses
784
+
785
+ - [x] The addresses form field (they can't be equal)
786
+ - [x] The shipping Address field (only required when checkbox is checked)
787
+ - [x] validation on all the address fields (street, number, etc) on both addresses
466
788
 
467
789
  ```typescript
468
- addressValidations(
469
- model.addresses?.billingAddress,
470
- 'addresses.billingAddress'
471
- );
472
- omitWhen(
473
- !model.addresses?.shippingAddressDifferentFromBillingAddress,
474
- () => {
475
- addressValidations(
476
- model.addresses?.shippingAddress,
477
- 'addresses.shippingAddress'
790
+ addressValidations(model.addresses?.billingAddress, 'addresses.billingAddress');
791
+ omitWhen(!model.addresses?.shippingAddressDifferentFromBillingAddress, () => {
792
+ addressValidations(
793
+ model.addresses?.shippingAddress,
794
+ 'addresses.shippingAddress'
795
+ );
796
+ test('addresses', 'The addresses appear to be the same', () => {
797
+ enforce(JSON.stringify(model.addresses?.billingAddress)).notEquals(
798
+ JSON.stringify(model.addresses?.shippingAddress)
478
799
  );
479
- test('addresses', 'The addresses appear to be the same', () => {
480
- enforce(JSON.stringify(model.addresses?.billingAddress)).notEquals(
481
- JSON.stringify(model.addresses?.shippingAddress)
482
- );
483
- });
484
- }
485
- );
800
+ });
801
+ });
486
802
  ```
487
803
 
488
804
  ### Validation options
489
805
 
490
- The validation is triggered immediately when the input on the formModel changes.
806
+ The validation is triggered immediately when the input on the formModel changes.
491
807
  In some cases you want to debounce the input (e.g. if you make an api call in the validation suite).
492
808
 
493
- You can configure additional `validationOptions` at various levels like `form`, `ngModelGroup` or `ngModel`.
809
+ You can configure additional `validationOptions` at various levels like `form`, `ngModelGroup` or `ngModel`.
494
810
 
495
811
  ```html
496
-
497
- <form scVestForm
498
- ...
499
- [validationOptions]="{ debounceTime: 0 }">
500
- ...
501
- <div sc-control-wrapper>
502
- <label>UserId</label>
503
- <input type="text" name="userId" [ngModel]="formValue().userId?"
504
- [validationOptions]="{ debounceTime: 300 }"/>
505
- </div>
506
- ...
812
+ <form scVestForm ... [validationOptions]="{ debounceTime: 0 }">
813
+ ...
814
+ <div sc-control-wrapper>
815
+ <label>UserId</label>
816
+ <input
817
+ type="text"
818
+ name="userId"
819
+ [ngModel]="formValue().userId"
820
+ [validationOptions]="{ debounceTime: 300 }"
821
+ />
822
+ </div>
823
+ ...
507
824
  </form>
508
825
  ```
509
826
 
510
-
511
827
  ### Validations on the root form
512
828
 
513
829
  When we want to validate multiple fields that are depending on each other,
514
830
  it is a best practice to wrap them in a parent form group.
515
- If `password` and `confirmPassword` have to be equal the validation should not happen on
831
+ If `password` and `confirmPassword` have to be equal the validation should not happen on
516
832
  `password` nor on `confirmPassword`, it should happen on `passwords`:
517
833
 
518
834
  ```typescript
@@ -520,8 +836,8 @@ const form = {
520
836
  // validation happens here
521
837
  passwords: {
522
838
  password: '',
523
- confirmPassword: ''
524
- }
839
+ confirmPassword: '',
840
+ },
525
841
  };
526
842
  ```
527
843
 
@@ -531,16 +847,19 @@ Use the `errorsChange` output to keep the errors as state in a signal that we ca
531
847
  wherever we want.
532
848
 
533
849
  ```html
534
- {{ errors()?.['rootForm'] }} <!-- render the errors on the rootForm -->
535
- {{ errors() }} <!-- render all the errors -->
536
- <form scVestForm
537
- [formValue]="formValue()"
538
- [validateRootForm]="true"
539
- [formShape]="shape"
540
- [suite]="suite"
541
- (errorsChange)="errors.set($event)"
542
- ...>
543
- </form>
850
+ {{ errors()?.['rootForm'] }}
851
+ <!-- render the errors on the rootForm -->
852
+ {{ errors() }}
853
+ <!-- render all the errors -->
854
+ <form
855
+ scVestForm
856
+ [formValue]="formValue()"
857
+ [validateRootForm]="true"
858
+ [formShape]="shape"
859
+ [suite]="suite"
860
+ (errorsChange)="errors.set($event)"
861
+ ...
862
+ ></form>
544
863
  ```
545
864
 
546
865
  ```typescript
@@ -548,7 +867,7 @@ export class MyformComponent {
548
867
  protected readonly formValue = signal<MyFormModel>({});
549
868
  protected readonly suite = myFormModelSuite;
550
869
  // Keep the errors in state
551
- protected readonly errors = signal<Record<string, string>>({ });
870
+ protected readonly errors = signal<Record<string, string>>({});
552
871
  }
553
872
  ```
554
873
 
@@ -562,14 +881,13 @@ import { ROOT_FORM } from 'ngx-vest-forms';
562
881
 
563
882
  test(ROOT_FORM, 'Brecht is not 30 anymore', () => {
564
883
  enforce(
565
- model.firstName === 'Brecht' &&
566
- model.lastName === 'Billiet' &&
567
- model.age === 30).isFalsy();
884
+ model.firstName === 'Brecht' &&
885
+ model.lastName === 'Billiet' &&
886
+ model.age === 30
887
+ ).isFalsy();
568
888
  });
569
889
  ```
570
890
 
571
-
572
-
573
891
  ### Validation of dependant controls and or groups
574
892
 
575
893
  Sometimes, form validations are dependent on the values of other form controls or groups.
@@ -578,8 +896,59 @@ A typical example is the `confirmPassword` field, which should only be validated
578
896
  When the `password` field value changes, it necessitates re-validating the `confirmPassword` field to ensure
579
897
  consistency.
580
898
 
581
- Here's how you can handle validation dependencies with ngx-vest-forms and vest.js:
899
+ #### Understanding the Architecture: Why `validationConfig` Is Needed
900
+
901
+ Before diving into the implementation, it's important to understand the architectural boundaries between Vest.js and Angular:
902
+
903
+ **What Vest.js Handles:**
904
+
905
+ - ✅ Validation logic and rules
906
+ - ✅ Conditional validation with `omitWhen()`, `skipWhen()`
907
+ - ✅ Field-level optimization with `only()`
908
+ - ✅ Async validations with AbortController
909
+ - ✅ Cross-field validation logic (e.g., "passwords must match")
910
+
911
+ **What Vest.js Cannot Do:**
912
+
913
+ - ❌ Trigger Angular to revalidate a different form control
914
+ - ❌ Control Angular's form control lifecycle
915
+ - ❌ Tell Angular "when field X changes, also validate field Y"
916
+
917
+ **Angular's Limitation:**
918
+ Angular template-driven forms do not natively know about cross-field dependencies. When a field changes, only its own validators run automatically.
919
+
920
+ **How `validationConfig` Bridges This Gap:**
921
+
922
+ The `validationConfig` tells Angular's form system: "when field X changes, also call `updateValueAndValidity()` on field Y". This ensures that:
923
+
924
+ - Cross-field validations run at the right time
925
+ - UI error states update correctly
926
+ - Form validation state remains consistent
927
+
928
+ **Example of the Problem:**
929
+
930
+ ```typescript
931
+ // In your Vest suite
932
+ test('confirmPassword', 'Passwords must match', () => {
933
+ enforce(model.confirmPassword).equals(model.password);
934
+ });
935
+ ```
936
+
937
+ Without `validationConfig`: If user changes `password`, the `confirmPassword` field won't be revalidated automatically, even though its validity depends on the password value.
938
+
939
+ With `validationConfig`: Angular knows to revalidate `confirmPassword` whenever `password` changes.
940
+
941
+ **Architectural Benefits of This Separation:**
942
+
943
+ This separation of concerns provides several advantages:
582
944
 
945
+ - **Clarity**: Vest.js focuses on validation logic, `validationConfig` handles Angular orchestration
946
+ - **Reusability**: Vest suites work across frameworks, while `validationConfig` is Angular-specific
947
+ - **Maintainability**: Changes to validation logic don't affect dependency management
948
+ - **Performance**: Only necessary validations run, only necessary controls revalidate
949
+ - **Testability**: Validation logic can be tested independently from Angular form behavior
950
+
951
+ Here's how you can handle validation dependencies with ngx-vest-forms and vest.js:
583
952
 
584
953
  Use Vest to create a suite where you define the conditional validations.
585
954
  For example, the `confirmPassword` field should only be validated when the `password` field is not empty.
@@ -589,27 +958,25 @@ Additionally, you need to ensure that both fields match.
589
958
  import { enforce, omitWhen, staticSuite, test } from 'vest';
590
959
  import { MyFormModel } from '../models/my-form.model';
591
960
 
592
- export const myFormModelSuite = staticSuite((model: MyFormModel, field?: string) => {
593
- if (field) {
594
- only(field);
595
- }
961
+ import { enforce, omitWhen, only, staticSuite, test } from 'vest';
596
962
 
597
963
  test('password', 'Password is required', () => {
598
- enforce(model.password).isNotBlank();
964
+ enforce(model.password).isNotBlank();
599
965
  });
600
966
 
601
967
  omitWhen(!model.password, () => {
602
- test('confirmPassword', 'Confirm password is required', () => {
603
- enforce(model.confirmPassword).isNotBlank();
604
- });
968
+ test('confirmPassword', 'Confirm password is required', () => {
969
+ enforce(model.confirmPassword).isNotBlank();
970
+ });
605
971
  });
606
972
 
607
973
  omitWhen(!model.password || !model.confirmPassword, () => {
608
- test('passwords', 'Passwords do not match', () => {
609
- enforce(model.confirmPassword).equals(model.password);
610
- });
974
+ test('passwords', 'Passwords do not match', () => {
975
+ enforce(model.confirmPassword).equals(model.password);
976
+ });
611
977
  });
612
- });
978
+ }
979
+ );
613
980
  ```
614
981
 
615
982
  Creating a validation config.
@@ -620,31 +987,82 @@ protected validationConfig = {
620
987
  password: ['passwords.confirmPassword']
621
988
  }
622
989
  ```
990
+
623
991
  Here we see that when password changes, it needs to update the field `passwords.confirmPassword`.
624
992
  This validationConfig is completely dynamic, and can also be used for form arrays.
625
993
 
626
994
  ```html
995
+ <form scVestForm ... [validationConfig]="validationConfig">
996
+ <div ngModelGroup="passwords">
997
+ <label>Password</label>
998
+ <input
999
+ type="password"
1000
+ name="password"
1001
+ [ngModel]="formValue().passwords?.password"
1002
+ />
1003
+
1004
+ <label>Confirm Password</label>
1005
+ <input
1006
+ type="password"
1007
+ name="confirmPassword"
1008
+ [ngModel]="formValue().passwords?.confirmPassword"
1009
+ />
1010
+ </div>
1011
+ </form>
1012
+ ```
1013
+
1014
+ #### Advanced State Management Patterns
1015
+
1016
+ The `validationConfig` works seamlessly with different state management approaches. By default, most examples show using a single signal for both input and output:
627
1017
 
1018
+ ```typescript
1019
+ // Standard pattern: single signal for both input and output
1020
+ protected readonly formValue = signal<MyFormModel>({});
1021
+
1022
+ // Template
628
1023
  <form scVestForm
629
- ...
1024
+ [formValue]="formValue()"
1025
+ (formValueChange)="formValue.set($event)"
630
1026
  [validationConfig]="validationConfig">
631
- <div ngModelGroup="passwords">
632
- <label>Password</label>
633
- <input type="password" name="password" [ngModel]="formValue().passwords?.password"/>
1027
+ ```
1028
+
1029
+ However, you can also use **separate signals** for input and output if your application architecture requires it:
1030
+
1031
+ ```typescript
1032
+ // Advanced pattern: separate input and output signals
1033
+ protected readonly inputFormValue = signal<MyFormModel>({});
1034
+ protected readonly outputFormValue = signal<MyFormModel>({});
634
1035
 
635
- <label>Confirm Password</label>
636
- <input type="password" name="confirmPassword" [ngModel]="formValue().passwords?.confirmPassword"/>
637
- </div>
1036
+ // Template
1037
+ <form scVestForm
1038
+ [formValue]="inputFormValue()"
1039
+ (formValueChange)="handleFormChange($event)"
1040
+ [validationConfig]="validationConfig">
1041
+ <input name="password" [ngModel]="outputFormValue().password" />
1042
+ <input name="confirmPassword" [ngModel]="outputFormValue().confirmPassword" />
638
1043
  </form>
1044
+
1045
+ // Component
1046
+ handleFormChange(value: MyFormModel) {
1047
+ // Update output signal independently
1048
+ this.outputFormValue.set(value);
1049
+ // Optionally sync input signal or perform other logic
1050
+ }
639
1051
  ```
640
1052
 
1053
+ **Why this works**: The validation configuration operates at the **form control level**, listening directly to Angular's form control changes rather than component signals. This makes it independent of your chosen state management pattern.
1054
+
1055
+ This pattern is useful when:
1056
+
1057
+ - You need different processing logic for form inputs vs outputs
1058
+ - You're integrating with state management libraries
1059
+ - You want to maintain separate concerns between form display and form handling
641
1060
 
642
1061
  #### Form array validations
643
1062
 
644
1063
  An example can be found [in this simplified courses article](https://blog.simplified.courses/template-driven-forms-with-form-arrays/)
645
1064
  There is also a complex example of form arrays with complex validations in the examples.
646
1065
 
647
-
648
1066
  ### Child form components
649
1067
 
650
1068
  Big forms result in big files. It makes sense to split them up.
@@ -665,23 +1083,101 @@ export class AddressComponent {
665
1083
  }
666
1084
  ```
667
1085
 
668
- # Examples
669
- to check the examples, clone this repo and run:
670
- ```shell
671
- npm i
1086
+ ## Resources
1087
+
1088
+ ### Documentation & Tutorials
1089
+
1090
+ - **[Angular Official Documentation](https://angular.dev/guide/forms)** - Template-driven forms guide
1091
+ - **[Vest.js Documentation](https://vestjs.dev)** - Validation framework used by ngx-vest-forms
1092
+ - **[Live Examples Repository](https://github.com/ngx-vest-forms/ngx-vest-forms/tree/master/projects/examples)** - Complex form examples and patterns
1093
+ - **[Interactive Stackblitz Demo](https://stackblitz.com/~/github.com/simplifiedcourses/ngx-vest-forms-stackblitz)** - Try it in your browser
1094
+
1095
+ ### Running Examples Locally
1096
+
1097
+ Clone this repo and run the examples:
1098
+
1099
+ ```bash
1100
+ npm install
672
1101
  npm start
673
1102
  ```
674
1103
 
675
- There is an example of a complex form with a lot of conditionals and specifics,
676
- and there is an example of a form array with complex validations that is used to
677
- create a form to add business hours. A free tutorial will follow soon.
1104
+ ### Learning Resources
1105
+
1106
+ [![Angular Forms Course](course.jpeg)](https://www.simplified.courses/complex-angular-template-driven-forms)
1107
+
1108
+ **[Complex Angular Template-Driven Forms Course](https://www.simplified.courses/complex-angular-template-driven-forms)** - Master advanced form patterns and become a form expert.
1109
+
1110
+ ### Founding Articles by Brecht Billiet
1111
+
1112
+ This library was originally created by [Brecht Billiet](https://twitter.com/brechtbilliet). Here are his foundational blog posts that inspired and guided the development:
1113
+
1114
+ - **[Introducing ngx-vest-forms](https://blog.simplified.courses/introducing-ngx-vest-forms/)** - The original introduction and motivation
1115
+ - **[Making Angular Template-Driven Forms Type-Safe](https://blog.simplified.courses/making-angular-template-driven-forms-typesafe/)** - Deep dive into type safety
1116
+ - **[Asynchronous Form Validators in Angular with Vest](https://blog.simplified.courses/asynchronous-form-validators-in-angular-with-vest/)** - Advanced async validation patterns
1117
+ - **[Template-Driven Forms with Form Arrays](https://blog.simplified.courses/template-driven-forms-with-form-arrays/)** - Dynamic form arrays implementation
1118
+
1119
+ ### Community & Support
1120
+
1121
+ - **[GitHub Issues](https://github.com/ngx-vest-forms/ngx-vest-forms/issues)** - Report bugs or request features
1122
+ - **[GitHub Discussions](https://github.com/ngx-vest-forms/ngx-vest-forms/discussions)** - Ask questions and share ideas
1123
+ - **[npm Package](https://www.npmjs.com/package/ngx-vest-forms)** - Official package page
1124
+
1125
+ ## Developer Resources
1126
+
1127
+ ### Comprehensive Instruction Files
1128
+
1129
+ This project includes detailed instruction files designed to help developers master ngx-vest-forms and Vest.js patterns:
1130
+
1131
+ - **[`.github/instructions/ngx-vest-forms.instructions.md`](.github/instructions/ngx-vest-forms.instructions.md)** - Complete guide for using ngx-vest-forms library
1132
+ - **[`.github/instructions/vest.instructions.md`](.github/instructions/vest.instructions.md)** - Comprehensive Vest.js validation patterns and best practices
1133
+ - **[`.github/copilot-instructions.md`](.github/copilot-instructions.md)** - Main GitHub Copilot instructions for this workspace
1134
+
1135
+ ### Using Instruction Files in Your Workspace
1136
+
1137
+ For the best development experience with ngx-vest-forms, **copy these instruction files to your own project's `.github/` directory**:
1138
+
1139
+ ```bash
1140
+ # Create the directories in your project
1141
+ mkdir -p .github/instructions
1142
+
1143
+ # Copy the instruction files
1144
+ curl -o .github/instructions/ngx-vest-forms.instructions.md \
1145
+ https://raw.githubusercontent.com/ngx-vest-forms/ngx-vest-forms/main/.github/instructions/ngx-vest-forms.instructions.md
1146
+
1147
+ curl -o .github/instructions/vest.instructions.md \
1148
+ https://raw.githubusercontent.com/ngx-vest-forms/ngx-vest-forms/main/.github/instructions/vest.instructions.md
1149
+
1150
+ # Optionally, adapt the main copilot instructions for your project
1151
+ curl -o .github/copilot-instructions.md \
1152
+ https://raw.githubusercontent.com/ngx-vest-forms/ngx-vest-forms/main/.github/copilot-instructions.md
1153
+ ```
1154
+
1155
+ **Benefits of copying instruction files:**
1156
+
1157
+ - **GitHub Copilot Integration** - Enhanced code generation aligned with best practices
1158
+ - **Comprehensive Documentation** - Complete patterns and examples at your fingertips
1159
+ - **Consistent Code Quality** - Maintain validation patterns and architectural standards
1160
+ - **Faster Development** - Quick reference for complex scenarios and optimizations
1161
+
1162
+ ## Acknowledgments
1163
+
1164
+ 🙏 **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.
1165
+
1166
+ ### Core Contributors & Inspirations
1167
+
1168
+ **[Evyatar Alush](https://twitter.com/evyataral)** - Creator of [Vest.js](https://vestjs.dev/)
1169
+
1170
+ - 🎯 **The validation engine** that powers ngx-vest-forms
1171
+ - 🎙️ **Featured on PodRocket**: [Vest with Evyatar Alush](https://dev.to/podrocket/vest-with-evyatar-alush) - Deep dive into the philosophy and architecture of Vest.js
1172
+
1173
+ **[Ward Bell](https://twitter.com/wardbell)** - Template-Driven Forms Advocate
678
1174
 
1175
+ - 📢 **Evangelized Template-Driven Forms**: [Prefer Template-Driven Forms](https://devconf.net/talk/prefer-template-driven-forms-ward-bell-ng-conf-2021) (ng-conf 2021)
1176
+ - 🎥 **Original Vest.js + Angular Integration**: [Form validation done right](https://www.youtube.com/watch?v=EMUAtQlh9Ko) - The foundational talk that inspired this approach
1177
+ - 💻 **Early Implementation**: [ngc-validate](https://github.com/wardbell/ngc-validate) - The initial version of template-driven forms with Vest.js
679
1178
 
680
- You can check the examples in the github repo [here](https://github.com/simplifiedcourses/ngx-vest-forms/blob/master/projects/examples).
681
- [Here](https://stackblitz.com/~/github.com/simplifiedcourses/ngx-vest-forms-stackblitz){:target="_blank"} is a stackblitz example for you.
682
- It's filled with form complexities and also contains form array logic.
1179
+ 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.
683
1180
 
684
- ## Want to learn more?
685
- [![course.jpeg](course.jpeg)](https://www.simplified.courses/complex-angular-template-driven-forms)
1181
+ ## License
686
1182
 
687
- [This course](https://www.simplified.courses/complex-angular-template-driven-forms) teaches you to become a form expert in no time.
1183
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.