feat(frontend): add reusable form validators

- Create emailValidator with stricter pattern than Angular's built-in
- Create nameValidator with configurable minLength, maxLength, allowNumbers
- Support for Spanish characters (accents, ñ, ü)
- Export validators from shared module
- Add JSDoc documentation with usage examples
This commit is contained in:
Andrés Eduardo García Márquez 2026-01-08 00:30:20 -05:00
parent bde0d53ba3
commit 3d4a8b56ff
4 changed files with 100 additions and 0 deletions

View File

@ -7,3 +7,7 @@ export * from './components/ui/connectivity-overlay/connectivity-overlay.compone
// Pipes // Pipes
export * from './pipes/credits.pipe'; export * from './pipes/credits.pipe';
export * from './pipes/initials.pipe'; export * from './pipes/initials.pipe';
// Validators
export * from './validators/email.validator';
export * from './validators/name.validator';

View File

@ -0,0 +1,30 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
/**
* Email validation pattern that matches standard email format.
* More strict than HTML5 default email validation.
*/
const EMAIL_PATTERN = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
/**
* Custom email validator that provides stricter validation than Angular's built-in.
* Validates format: local@domain.tld
*
* @returns ValidatorFn that returns null if valid, or { email: true } if invalid
*
* @example
* ```typescript
* this.form = this.fb.group({
* email: ['', [Validators.required, emailValidator()]]
* });
* ```
*/
export function emailValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) {
return null; // Let required validator handle empty values
}
const isValid = EMAIL_PATTERN.test(control.value);
return isValid ? null : { email: true };
};
}

View File

@ -0,0 +1,2 @@
export * from './email.validator';
export * from './name.validator';

View File

@ -0,0 +1,64 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
/**
* Name validation options.
*/
export interface NameValidatorOptions {
/** Minimum length required (default: 3) */
minLength?: number;
/** Maximum length allowed (default: 100) */
maxLength?: number;
/** Whether to allow numbers (default: false) */
allowNumbers?: boolean;
}
const DEFAULT_OPTIONS: Required<NameValidatorOptions> = {
minLength: 3,
maxLength: 100,
allowNumbers: false,
};
/**
* Custom name validator with configurable options.
* Validates that name contains only letters, spaces, and common punctuation.
*
* @param options - Validation options
* @returns ValidatorFn that returns validation errors or null
*
* @example
* ```typescript
* this.form = this.fb.group({
* name: ['', [Validators.required, nameValidator({ minLength: 2 })]]
* });
* ```
*/
export function nameValidator(options: NameValidatorOptions = {}): ValidatorFn {
const opts = { ...DEFAULT_OPTIONS, ...options };
return (control: AbstractControl): ValidationErrors | null => {
if (!control.value) {
return null; // Let required validator handle empty values
}
const value = control.value.trim();
if (value.length < opts.minLength) {
return { minlength: { requiredLength: opts.minLength, actualLength: value.length } };
}
if (value.length > opts.maxLength) {
return { maxlength: { requiredLength: opts.maxLength, actualLength: value.length } };
}
// Pattern: letters (including accented), spaces, hyphens, apostrophes
const pattern = opts.allowNumbers
? /^[a-zA-ZáéíóúÁÉÍÓÚñÑüÜ0-9\s'-]+$/
: /^[a-zA-ZáéíóúÁÉÍÓÚñÑüÜ\s'-]+$/;
if (!pattern.test(value)) {
return { pattern: true };
}
return null;
};
}