From b8741f443d6a23371e738e4aace9b963482803b5 Mon Sep 17 00:00:00 2001 From: Michael Small Date: Mon, 1 Apr 2024 19:53:01 -0500 Subject: [PATCH 1/4] feat: add `email` validator --- apps/example/src/app/app.routes.ts | 4 ++ .../app/validators/validators.component.ts | 40 +++++++++++++++++++ packages/platform/src/lib/validators/email.ts | 29 ++++++++++++++ packages/platform/src/lib/validators/index.ts | 1 + 4 files changed, 74 insertions(+) create mode 100644 apps/example/src/app/validators/validators.component.ts create mode 100644 packages/platform/src/lib/validators/email.ts diff --git a/apps/example/src/app/app.routes.ts b/apps/example/src/app/app.routes.ts index 95b975c..1049a3a 100644 --- a/apps/example/src/app/app.routes.ts +++ b/apps/example/src/app/app.routes.ts @@ -22,4 +22,8 @@ export const routes: Routes = [ path: 'form-with-cva', loadComponent: () => import('./form-with-cva/form-with-cva.component'), }, + { + path: 'validators', + loadComponent: () => import('./validators/validators.component'), + }, ]; diff --git a/apps/example/src/app/validators/validators.component.ts b/apps/example/src/app/validators/validators.component.ts new file mode 100644 index 0000000..115e91d --- /dev/null +++ b/apps/example/src/app/validators/validators.component.ts @@ -0,0 +1,40 @@ +import { Component } from '@angular/core'; +import { JsonPipe } from '@angular/common'; +import { + createFormField, + createFormGroup, + SignalInputDebounceDirective, + SignalInputDirective, + SignalInputErrorDirective, + Validators, + withErrorComponent, +} from '@ng-signal-forms'; +import { FormsModule } from '@angular/forms'; +import { CustomErrorComponent } from '../custom-input-error.component'; + +@Component({ + selector: 'validators', + standalone: true, + imports: [ + JsonPipe, + FormsModule, + SignalInputDirective, + SignalInputErrorDirective, + SignalInputDebounceDirective, + ], + template: ` +
+ + +
+
{{ form.value() | json }}
+
{{ form.errorsArray() | json }}
+ `, + styles: [], + providers: [withErrorComponent(CustomErrorComponent)], +}) +export default class ValidatorsComponent { + form = createFormGroup({ + email: createFormField('', { validators: [Validators.email()] }), + }); +} diff --git a/packages/platform/src/lib/validators/email.ts b/packages/platform/src/lib/validators/email.ts new file mode 100644 index 0000000..8b379db --- /dev/null +++ b/packages/platform/src/lib/validators/email.ts @@ -0,0 +1,29 @@ +import { SetValidationState, ValidatorFn } from '../validation'; + +/** + * @description Email regex is directly from the Angular Forms definition + * @link https://github.com/angular/angular/blob/17.3.2/packages/forms/src/validators.ts#L126 + */ +const EMAIL_REGEXP = + /^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; + +export function email(): ValidatorFn { + return (value: unknown, setState: SetValidationState) => { + const valid = + value === null || + value === undefined || + typeof value !== 'string' || + EMAIL_REGEXP.test(value); + + if (valid) { + setState('VALID'); + } else { + // TODO - I do not fully trust that I understand what the shape of the `ValidationErrors` should be + setState('INVALID', { + email: { + details: true, + }, + }); + } + }; +} diff --git a/packages/platform/src/lib/validators/index.ts b/packages/platform/src/lib/validators/index.ts index 8dd87a9..04bb819 100644 --- a/packages/platform/src/lib/validators/index.ts +++ b/packages/platform/src/lib/validators/index.ts @@ -1,3 +1,4 @@ +export * from './email'; export * from './equals-to'; export * from './length'; export * from './max'; From 8a80df9421a7061c5e41b55a49a1b48f01ecc643 Mon Sep 17 00:00:00 2001 From: Michael Small Date: Mon, 1 Apr 2024 20:02:29 -0500 Subject: [PATCH 2/4] fix: change invalid email error details to reflect current value --- packages/platform/src/lib/validators/email.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/platform/src/lib/validators/email.ts b/packages/platform/src/lib/validators/email.ts index 8b379db..60fc865 100644 --- a/packages/platform/src/lib/validators/email.ts +++ b/packages/platform/src/lib/validators/email.ts @@ -18,10 +18,11 @@ export function email(): ValidatorFn { if (valid) { setState('VALID'); } else { - // TODO - I do not fully trust that I understand what the shape of the `ValidationErrors` should be setState('INVALID', { email: { - details: true, + details: { + currentValue: value, + }, }, }); } From 01bb51bdca0d1649df448cd94ad1daafe8904deb Mon Sep 17 00:00:00 2001 From: Michael Small Date: Mon, 1 Apr 2024 20:33:54 -0500 Subject: [PATCH 3/4] fix: clarify tsdoc description of email validator regex --- packages/platform/src/lib/validators/email.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/platform/src/lib/validators/email.ts b/packages/platform/src/lib/validators/email.ts index 60fc865..6401ac7 100644 --- a/packages/platform/src/lib/validators/email.ts +++ b/packages/platform/src/lib/validators/email.ts @@ -1,7 +1,8 @@ import { SetValidationState, ValidatorFn } from '../validation'; /** - * @description Email regex is directly from the Angular Forms definition + * @description Email regex pattern is directly from the Angular Forms definition. + * Note: other assumptions on validation differ from Angular reactive forms. * @link https://github.com/angular/angular/blob/17.3.2/packages/forms/src/validators.ts#L126 */ const EMAIL_REGEXP = From 564723b89b464d410e5a68dde3d21f69bbd4b154 Mon Sep 17 00:00:00 2001 From: Michael Small Date: Mon, 1 Apr 2024 20:35:44 -0500 Subject: [PATCH 4/4] fix: compare reactive forms email validation with new validator --- .../app/validators/validators.component.ts | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/example/src/app/validators/validators.component.ts b/apps/example/src/app/validators/validators.component.ts index 115e91d..8f90695 100644 --- a/apps/example/src/app/validators/validators.component.ts +++ b/apps/example/src/app/validators/validators.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { JsonPipe } from '@angular/common'; import { createFormField, @@ -9,26 +9,43 @@ import { Validators, withErrorComponent, } from '@ng-signal-forms'; -import { FormsModule } from '@angular/forms'; +import { + FormBuilder, + FormControl, + FormsModule, + ReactiveFormsModule, + Validators as ReactiveFormValidators, +} from '@angular/forms'; import { CustomErrorComponent } from '../custom-input-error.component'; @Component({ selector: 'validators', standalone: true, imports: [ - JsonPipe, FormsModule, + JsonPipe, SignalInputDirective, SignalInputErrorDirective, SignalInputDebounceDirective, + // TODO - remove before PR + ReactiveFormsModule, ], template: `
- +
{{ form.value() | json }}
{{ form.errorsArray() | json }}
+ + +
+ + +
+
{{ reactiveForm.value | json }}
+
{{ reactiveForm.errors | json }}
+
{{ reactiveForm.controls.email.errors | json }}
`, styles: [], providers: [withErrorComponent(CustomErrorComponent)], @@ -37,4 +54,10 @@ export default class ValidatorsComponent { form = createFormGroup({ email: createFormField('', { validators: [Validators.email()] }), }); + + #fb = inject(FormBuilder); + + reactiveForm = this.#fb.group({ + email: new FormControl('', { validators: ReactiveFormValidators.email }), + }); }