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:
parent
bde0d53ba3
commit
3d4a8b56ff
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './email.validator';
|
||||||
|
export * from './name.validator';
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue