Skip to content

Commit 9973941

Browse files
authored
Merge pull request #50 from abarghoud/develop
v1.9.1
2 parents 4710f97 + b8a31a1 commit 9973941

13 files changed

+171
-23
lines changed

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ A lightweight library for dynamically validate Angular reactive forms using [cla
2727
- [Creating a ClassValidatorFormGroup](#creating-a-classvalidatorformgroup)
2828
- [Using ClassValidatorFormBuilderService](#using-classvalidatorformbuilderservice)
2929
- [Using ClassValidatorFormGroup class](#using-classvalidatorformgroup-class)
30+
- [Eager Validation Option]()
3031
- [Add custom validators](#add-custom-validators)
3132
- [Providing validators when creating the ClassValidatorFormControl](#providing-validators-when-creating-the-classvalidatorformcontrol)
3233
- [Providing validators using `setValidators`/`setValidatorsWithDynamicValidation` methods](#providing-validators-using-setvalidatorssetvalidatorswithdynamicvalidation-methods)
@@ -143,6 +144,48 @@ Now, setting value to any of form controls, will perfom the validator set in the
143144

144145
this.profileForm.controls.email.setValue('email@email.com');
145146
console.log(this.profileForm.controls.email) // null
147+
148+
#### Eager Validation Option
149+
150+
By default, `ngx-reactive-form-class-validator` validates form controls after the form is fully initialized (ngAfterViewInit).
151+
If you want validation to run immediately after form initialization (for example, in ngAfterViewInit or just after you create a FormGroup), you can enable eager validation at the ClassValidatorFormGroup/FormBuilder level.
152+
153+
```
154+
import { ClassValidatorFormGroup, ClassValidatorFormControl } from 'ngx-reactive-form-class-validator';
155+
156+
const formGroup = new ClassValidatorFormGroup({
157+
email: new ClassValidatorFormControl(''),
158+
password: new ClassValidatorFormControl('')
159+
}, null, { eagerValidation: true }); // 👈 Enable eager validation here
160+
161+
162+
// Or using the form builder
163+
164+
public constructor(
165+
private fb: ClassValidatorFormBuilderService,
166+
) { }
167+
168+
profileForm = this.fb.group(
169+
Profile,
170+
{
171+
firstName: [''],
172+
lastName: [''],
173+
email: [''],
174+
address: this.fb.group(Address,
175+
{
176+
street: [''],
177+
city: [''],
178+
state: [''],
179+
zip: ['']
180+
}
181+
),
182+
},
183+
undefined,
184+
{ eagerValidation: true } // 👈 Enable eager validation here
185+
);
186+
187+
```
188+
146189
### Add custom validators
147190
It is possible as well to combine dynamic validation with custom validation.
148191
There are several ways to do it:

apps/test/src/app/app.component.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ export class AppComponent implements OnInit {
7575
// using a FormControl will not apply dynamic validation
7676
zip: new FormControl("")
7777
})
78-
});
78+
}, undefined);
7979

8080
this.addressForm = this.profileForm.get(
8181
"address"
8282
) as ClassValidatorFormGroup;
83+
debugger;
8384
}
8485

8586
public clearValidators(): void {

libs/ngx-reactive-form-class-validator/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "ngx-reactive-form-class-validator",
33
"description": "A lightweight library for dynamically validate Angular reactive forms using class-validator library.",
44
"license": "MIT",
5-
"version": "1.9.0",
5+
"version": "1.9.1",
66
"keywords": [
77
"ng",
88
"angular",

libs/ngx-reactive-form-class-validator/src/lib/class-validator-form-builder.service.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ClassValidatorFormGroup } from './class-validator-form-group';
1414
import { ClassValidatorFormControl } from './class-validator-form-control';
1515
import { ClassValidatorFormArray } from './class-validator-form-array';
1616
import { ClassType } from './types';
17+
import { ClassValidatorFormGroupOptions } from './class-validator-form-group-options.interface';
1718

1819
// Coming from https://github.com/angular/angular/blob/3b0b7d22109c79b4dceb4ae069c3927894cf1bd6/packages/forms/src/form_builder.ts#L14
1920
const isAbstractControlOptions = (options: AbstractControlOptions | { [key: string]: any }): options is AbstractControlOptions =>
@@ -44,11 +45,16 @@ export class ClassValidatorFormBuilderService {
4445
* * `validator`: A synchronous validator function, or an array of validator functions
4546
* * `asyncValidator`: A single async validator or array of async validator functions
4647
*
48+
* @param classValidatorGroupOptions Options object of type `ClassValidatorFormGroupOptions` allowing
49+
* to define eagerValidation that validate controls immediately upon creation. Default is false (validators are executed starting from ngAfterViewInit hook)
50+
* See https://github.com/abarghoud/ngx-reactive-form-class-validator/issues/47
51+
*
4752
*/
4853
public group(
4954
formClassType: ClassType<any>,
5055
controlsConfig: { [p: string]: any },
51-
options?: AbstractControlOptions | { [p: string]: any } | null
56+
options?: AbstractControlOptions | { [p: string]: any } | null,
57+
classValidatorGroupOptions?: ClassValidatorFormGroupOptions,
5258
): ClassValidatorFormGroup {
5359
// Coming from https://github.com/angular/angular/blob/3b0b7d22109c79b4dceb4ae069c3927894cf1bd6/packages/forms/src/form_builder.ts#L59
5460
const controls = this.reduceControls(controlsConfig);
@@ -70,7 +76,7 @@ export class ClassValidatorFormBuilderService {
7076
}
7177
}
7278

73-
return new ClassValidatorFormGroup(formClassType, controls, { asyncValidators, updateOn, validators });
79+
return new ClassValidatorFormGroup(formClassType, controls, { asyncValidators, updateOn, validators }, undefined, classValidatorGroupOptions);
7480
}
7581

7682
/**

libs/ngx-reactive-form-class-validator/src/lib/class-validator-form-control.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,13 @@ export class ClassValidatorFormControl<T = any> extends FormControl<T | any> {
3838
/**
3939
* @internal
4040
*/
41-
public setNameAndFormGroupClassValue(name: string, value: any): void {
41+
public setNameAndFormGroupClassValue(name: string, value: any, eagerValidation: boolean = false): void {
4242
this.name = name;
4343
this.formGroupClassValue = value;
44+
45+
if (eagerValidation) {
46+
this.updateValueAndValidity();
47+
}
4448
}
4549

4650
/**
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export interface ClassValidatorFormGroupOptions {
2+
eagerValidation?: boolean; // default: false
3+
}

libs/ngx-reactive-form-class-validator/src/lib/class-validator-form-group.spec.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FakeUser } from './testing/fake-user-testing.model';
1+
import { FakeThing, FakeUser } from './testing/fake-user-testing.model';
22
import { fakeUserFormControls } from './testing/fake-form-testing.fixture';
33
import { ClassValidatorFormGroup } from './class-validator-form-group';
44
import { ClassValidatorFormControl } from './class-validator-form-control';
@@ -20,8 +20,8 @@ describe('The ClassValidatorFormGroup class', () => {
2020
expectedClassValue.firstName = fakeUserFormControls.firstName.value;
2121
expectedClassValue.id = fakeUserFormControls.id.value;
2222

23-
expect(firstNameSetNameAndClassValueSpy).toBeCalledWith('firstName', expectedClassValue);
24-
expect(idSetNameAndClassValueSpy).toBeCalledWith('id', expectedClassValue);
23+
expect(firstNameSetNameAndClassValueSpy).toBeCalledWith('firstName', expectedClassValue, undefined);
24+
expect(idSetNameAndClassValueSpy).toBeCalledWith('id', expectedClassValue, undefined);
2525
});
2626
});
2727

@@ -42,7 +42,7 @@ describe('The ClassValidatorFormGroup class', () => {
4242
const expectedClassValue = new FakeUser();
4343
expectedClassValue.firstName = 'name';
4444

45-
expect(formControlSetNameAndClassValueSpy).toBeCalledWith('firstName', expectedClassValue);
45+
expect(formControlSetNameAndClassValueSpy).toBeCalledWith('firstName', expectedClassValue, undefined);
4646
});
4747
});
4848

@@ -66,8 +66,41 @@ describe('The ClassValidatorFormGroup class', () => {
6666
expectedClassValue.id = fakeUserFormControls.id.value;
6767
expectedClassValue.isSessionLocked = fakeUserFormControls.isSessionLocked.value;
6868

69-
expect(idSetNameAndClassValueSpy).toBeCalledWith('id', expectedClassValue);
70-
expect(isSessionLockedSetNameAndClassValueSpy).toBeCalledWith('isSessionLocked', expectedClassValue);
69+
expect(idSetNameAndClassValueSpy).toBeCalledWith('id', expectedClassValue, undefined);
70+
expect(isSessionLockedSetNameAndClassValueSpy).toBeCalledWith('isSessionLocked', expectedClassValue, undefined);
71+
});
72+
});
73+
74+
describe('The formGroup', () => {
75+
let formGroup: ClassValidatorFormGroup;
76+
77+
describe('When FormControls are created normally', () => {
78+
beforeEach(() => {
79+
formGroup = new ClassValidatorFormGroup(FakeThing, {
80+
first: new ClassValidatorFormControl('notemail'),
81+
last: new ClassValidatorFormControl('')
82+
});
83+
})
84+
85+
it('should not run validators immediately', () => {
86+
expect(formGroup.valid).toBe(true);
87+
expect(formGroup.controls['first'].value).toBe('notemail');
88+
});
89+
});
90+
91+
describe('When FormControls are created with eagerValidation flag', () => {
92+
beforeEach(() => {
93+
formGroup = new ClassValidatorFormGroup(FakeThing, {
94+
first: new ClassValidatorFormControl('notemail'),
95+
last: new ClassValidatorFormControl('')
96+
}, undefined, undefined, { eagerValidation: true });
97+
})
98+
99+
it('should run validators immediately', () => {
100+
expect(formGroup.valid).toBe(false);
101+
expect(formGroup.controls['first'].value).toBe('notemail');
102+
expect(formGroup.controls['first'].errors.isEmail).toBeDefined();
103+
});
71104
});
72105
});
73106
});

libs/ngx-reactive-form-class-validator/src/lib/class-validator-form-group.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99

1010
import { ClassValidatorFormControl } from './class-validator-form-control';
1111
import { ClassType } from './types';
12+
import { ClassValidatorFormGroupOptions } from './class-validator-form-group-options.interface';
1213

1314
export class ClassValidatorFormGroup<TControl extends {
1415
[K in keyof TControl]: AbstractControl<any>;
@@ -28,12 +29,17 @@ export class ClassValidatorFormGroup<TControl extends {
2829
*
2930
* @param asyncValidator A single async validator or array of async validator functions
3031
*
32+
* @param options Options object of type `ClassValidatorFormGroupOptions` allowing
33+
* to define eagerValidation that validate controls immediately upon creation. Default is false (validators are executed starting from ngAfterViewInit hook)
34+
* See https://github.com/abarghoud/ngx-reactive-form-class-validator/issues/47
35+
*
3136
*/
3237
public constructor(
3338
private readonly formClassType: ClassType<any>,
3439
controls: TControl,
3540
validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null,
3641
asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null,
42+
private readonly options?: ClassValidatorFormGroupOptions,
3743
) {
3844
super(controls, validatorOrOpts, asyncValidator);
3945

@@ -88,8 +94,7 @@ export class ClassValidatorFormGroup<TControl extends {
8894
private setClassValidatorControlsContainerGroupClassValue(): void {
8995
Object.entries(this.controls).forEach(([controlName, control]) => {
9096
if (control instanceof ClassValidatorFormControl) {
91-
(this.controls[controlName] as ClassValidatorFormControl)
92-
.setNameAndFormGroupClassValue(controlName, this.classValue);
97+
control.setNameAndFormGroupClassValue(controlName, this.classValue, this.options?.eagerValidation);
9398
}
9499
});
95100
}

libs/ngx-reactive-form-class-validator/src/lib/testing/fake-form-testing.fixture.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ClassValidatorFormArray } from '../class-validator-form-array';
55
import { ClassValidatorFormGroup } from '../class-validator-form-group';
66
import { ClassValidatorFormControl } from '../class-validator-form-control';
77
import { ClassValidatorUntypedFormControl } from '../untyped/class-validator-untyped-form-control';
8+
import { ClassValidatorUntypedFormArray, ClassValidatorUntypedFormGroup } from '../untyped';
89

910
export const fakeContactFormGroup = new ClassValidatorFormArray([
1011
new ClassValidatorFormGroup(FakeContact, {
@@ -23,8 +24,8 @@ export const fakeUserFormControls = {
2324
contacts: new ClassValidatorFormArray([fakeContactFormGroup]),
2425
};
2526

26-
export const fakeContactUntypedFormGroup = new ClassValidatorFormArray([
27-
new ClassValidatorFormGroup(FakeContact, {
27+
export const fakeContactUntypedFormGroup = new ClassValidatorUntypedFormArray([
28+
new ClassValidatorUntypedFormGroup(FakeContact, {
2829
phoneNumber: new ClassValidatorUntypedFormControl(''),
2930
email: new ClassValidatorUntypedFormControl(''),
3031
type: new ClassValidatorUntypedFormControl(FakeContactType.phone),

libs/ngx-reactive-form-class-validator/src/lib/testing/fake-user-testing.model.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,12 @@ export class FakeUser {
4848
@MinLength(10)
4949
public username: string;
5050
}
51+
52+
export class FakeThing {
53+
@IsNotEmpty()
54+
@IsEmail()
55+
public first: string;
56+
57+
@IsNotEmpty()
58+
public last: string;
59+
}

0 commit comments

Comments
 (0)