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..8f90695 --- /dev/null +++ b/apps/example/src/app/validators/validators.component.ts @@ -0,0 +1,63 @@ +import { Component, inject } from '@angular/core'; +import { JsonPipe } from '@angular/common'; +import { + createFormField, + createFormGroup, + SignalInputDebounceDirective, + SignalInputDirective, + SignalInputErrorDirective, + Validators, + withErrorComponent, +} from '@ng-signal-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: [ + 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)], +}) +export default class ValidatorsComponent { + form = createFormGroup({ + email: createFormField('', { validators: [Validators.email()] }), + }); + + #fb = inject(FormBuilder); + + reactiveForm = this.#fb.group({ + email: new FormControl('', { validators: ReactiveFormValidators.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..6401ac7 --- /dev/null +++ b/packages/platform/src/lib/validators/email.ts @@ -0,0 +1,31 @@ +import { SetValidationState, ValidatorFn } from '../validation'; + +/** + * @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 = + /^(?=.{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 { + setState('INVALID', { + email: { + details: { + currentValue: value, + }, + }, + }); + } + }; +} 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';