ngx-vest-forms 1.0.3 → 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 (32) hide show
  1. package/README.md +747 -155
  2. package/fesm2022/ngx-vest-forms.mjs +149 -84
  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 -42
  9. package/esm2022/lib/directives/form-model.directive.mjs +0 -42
  10. package/esm2022/lib/directives/form.directive.mjs +0 -176
  11. package/esm2022/lib/directives/validate-root-form.directive.mjs +0 -84
  12. package/esm2022/lib/exports.mjs +0 -70
  13. package/esm2022/lib/utils/array-to-object.mjs +0 -4
  14. package/esm2022/lib/utils/deep-partial.mjs +0 -2
  15. package/esm2022/lib/utils/deep-required.mjs +0 -2
  16. package/esm2022/lib/utils/form-utils.mjs +0 -180
  17. package/esm2022/lib/utils/shape-validation.mjs +0 -61
  18. package/esm2022/ngx-vest-forms.mjs +0 -5
  19. package/esm2022/public-api.mjs +0 -14
  20. package/lib/components/control-wrapper/control-wrapper.component.d.ts +0 -18
  21. package/lib/constants.d.ts +0 -1
  22. package/lib/directives/form-model-group.directive.d.ts +0 -13
  23. package/lib/directives/form-model.directive.d.ts +0 -13
  24. package/lib/directives/form.directive.d.ts +0 -88
  25. package/lib/directives/validate-root-form.directive.d.ts +0 -24
  26. package/lib/exports.d.ts +0 -17
  27. package/lib/utils/array-to-object.d.ts +0 -3
  28. package/lib/utils/deep-partial.d.ts +0 -8
  29. package/lib/utils/deep-required.d.ts +0 -7
  30. package/lib/utils/form-utils.d.ts +0 -36
  31. package/lib/utils/shape-validation.d.ts +0 -15
  32. package/public-api.d.ts +0 -12
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
13
38
 
14
- You can install the package by running:
39
+ ### Why Choose ngx-vest-forms?
15
40
 
16
- ```shell
17
- npm i ngx-vest-forms
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;
18
55
  ```
19
56
 
20
- ### Creating a simple form
57
+ ## Getting Started
58
+
59
+ ### Prerequisites
21
60
 
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`
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
70
+ ```
26
71
 
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**
72
+ ### Quick Start
73
+
74
+ Create your first ngx-vest-forms component in 3 simple steps:
75
+
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`.
67
143
 
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:
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
161
+
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
+ }
465
+ ```
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
+ }
240
500
  ```
241
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,36 +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
+ });
802
+ ```
803
+
804
+ ### Validation options
805
+
806
+ The validation is triggered immediately when the input on the formModel changes.
807
+ In some cases you want to debounce the input (e.g. if you make an api call in the validation suite).
808
+
809
+ You can configure additional `validationOptions` at various levels like `form`, `ngModelGroup` or `ngModel`.
810
+
811
+ ```html
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
+ ...
824
+ </form>
486
825
  ```
487
826
 
488
827
  ### Validations on the root form
489
828
 
490
829
  When we want to validate multiple fields that are depending on each other,
491
830
  it is a best practice to wrap them in a parent form group.
492
- 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
493
832
  `password` nor on `confirmPassword`, it should happen on `passwords`:
494
833
 
495
834
  ```typescript
@@ -497,8 +836,8 @@ const form = {
497
836
  // validation happens here
498
837
  passwords: {
499
838
  password: '',
500
- confirmPassword: ''
501
- }
839
+ confirmPassword: '',
840
+ },
502
841
  };
503
842
  ```
504
843
 
@@ -508,16 +847,19 @@ Use the `errorsChange` output to keep the errors as state in a signal that we ca
508
847
  wherever we want.
509
848
 
510
849
  ```html
511
- {{ errors()?.['rootForm'] }} <!-- render the errors on the rootForm -->
512
- {{ errors() }} <!-- render all the errors -->
513
- <form scVestForm
514
- [formValue]="formValue()"
515
- [validateRootForm]="true"
516
- [formShape]="shape"
517
- [suite]="suite"
518
- (errorsChange)="errors.set($event)"
519
- ...>
520
- </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>
521
863
  ```
522
864
 
523
865
  ```typescript
@@ -525,7 +867,7 @@ export class MyformComponent {
525
867
  protected readonly formValue = signal<MyFormModel>({});
526
868
  protected readonly suite = myFormModelSuite;
527
869
  // Keep the errors in state
528
- protected readonly errors = signal<Record<string, string>>({ });
870
+ protected readonly errors = signal<Record<string, string>>({});
529
871
  }
530
872
  ```
531
873
 
@@ -539,29 +881,187 @@ import { ROOT_FORM } from 'ngx-vest-forms';
539
881
 
540
882
  test(ROOT_FORM, 'Brecht is not 30 anymore', () => {
541
883
  enforce(
542
- model.firstName === 'Brecht' &&
543
- model.lastName === 'Billiet' &&
544
- model.age === 30).isFalsy();
884
+ model.firstName === 'Brecht' &&
885
+ model.lastName === 'Billiet' &&
886
+ model.age === 30
887
+ ).isFalsy();
545
888
  });
546
889
  ```
547
890
 
548
- ### Validation dependencies
891
+ ### Validation of dependant controls and or groups
549
892
 
550
- Sometimes we need to re-trigger validations of form controls or form groups because they are dependant on other form controls or form groups.
551
- For instance: A `confirmPassword` field is not required unless the `password` is filled in. Which means that when the `password` field gets a new value,
552
- we need to run the validations on `confirmPassword`.
893
+ Sometimes, form validations are dependent on the values of other form controls or groups.
894
+ This scenario is common when a field's validity relies on the input of another field.
895
+ A typical example is the `confirmPassword` field, which should only be validated if the `password` field is filled in.
896
+ When the `password` field value changes, it necessitates re-validating the `confirmPassword` field to ensure
897
+ consistency.
553
898
 
899
+ #### Understanding the Architecture: Why `validationConfig` Is Needed
554
900
 
555
- ### Form arrays
901
+ Before diving into the implementation, it's important to understand the architectural boundaries between Vest.js and Angular:
556
902
 
557
- Todo
903
+ **What Vest.js Handles:**
558
904
 
559
- #### Form array validations
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")
560
910
 
561
- An example can be found [in this simplified courses article](https://blog.simplified.courses/template-driven-forms-with-form-arrays/)
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
+ ```
562
936
 
563
- We can look in `projects/examples/src/app/validations/phonenumber.validations.ts` to see an example on the validations part.
937
+ Without `validationConfig`: If user changes `password`, the `confirmPassword` field won't be revalidated automatically, even though its validity depends on the password value.
564
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:
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:
952
+
953
+ Use Vest to create a suite where you define the conditional validations.
954
+ For example, the `confirmPassword` field should only be validated when the `password` field is not empty.
955
+ Additionally, you need to ensure that both fields match.
956
+
957
+ ```typescript
958
+ import { enforce, omitWhen, staticSuite, test } from 'vest';
959
+ import { MyFormModel } from '../models/my-form.model';
960
+
961
+ import { enforce, omitWhen, only, staticSuite, test } from 'vest';
962
+
963
+ test('password', 'Password is required', () => {
964
+ enforce(model.password).isNotBlank();
965
+ });
966
+
967
+ omitWhen(!model.password, () => {
968
+ test('confirmPassword', 'Confirm password is required', () => {
969
+ enforce(model.confirmPassword).isNotBlank();
970
+ });
971
+ });
972
+
973
+ omitWhen(!model.password || !model.confirmPassword, () => {
974
+ test('passwords', 'Passwords do not match', () => {
975
+ enforce(model.confirmPassword).equals(model.password);
976
+ });
977
+ });
978
+ }
979
+ );
980
+ ```
981
+
982
+ Creating a validation config.
983
+ The `scVestForm` has an input called `validationConfig`, that we can use to let the system know when to retrigger validations.
984
+
985
+ ```typescript
986
+ protected validationConfig = {
987
+ password: ['passwords.confirmPassword']
988
+ }
989
+ ```
990
+
991
+ Here we see that when password changes, it needs to update the field `passwords.confirmPassword`.
992
+ This validationConfig is completely dynamic, and can also be used for form arrays.
993
+
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:
1017
+
1018
+ ```typescript
1019
+ // Standard pattern: single signal for both input and output
1020
+ protected readonly formValue = signal<MyFormModel>({});
1021
+
1022
+ // Template
1023
+ <form scVestForm
1024
+ [formValue]="formValue()"
1025
+ (formValueChange)="formValue.set($event)"
1026
+ [validationConfig]="validationConfig">
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>({});
1035
+
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" />
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
+ }
1051
+ ```
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
1060
+
1061
+ #### Form array validations
1062
+
1063
+ An example can be found [in this simplified courses article](https://blog.simplified.courses/template-driven-forms-with-form-arrays/)
1064
+ There is also a complex example of form arrays with complex validations in the examples.
565
1065
 
566
1066
  ### Child form components
567
1067
 
@@ -583,9 +1083,101 @@ export class AddressComponent {
583
1083
  }
584
1084
  ```
585
1085
 
586
- You can check the examples in the github repo [here](https://github.com/simplifiedcourses/ngx-vest-forms/blob/master/projects/examples).
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
1101
+ npm start
1102
+ ```
1103
+
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
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
1178
+
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.
587
1180
 
588
- ## Want to learn more?
589
- [![course.jpeg](course.jpeg)](https://www.simplified.courses/complex-angular-template-driven-forms)
1181
+ ## License
590
1182
 
591
- [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.