CustomValidators
Paquetes: @tiendana/ngx-core/validators @tiendana/utils
Clase de métodos estáticos con validadores síncronos y asíncronos para casos comunes en formularios reactivos: comparación de campos, fechas, archivos, unicidad remota y NIT colombiano.
Características
✅ Comparación entre campos con el error escrito en el control destino
✅ Fechas con coerción a UTC para cadenas sin zona horaria
✅ Archivos por tamaño, extensión y cabecera binaria
✅ Unicidad remota con debounce de 1 segundo
✅ NIT colombiano con dígito de verificación
import { CustomValidators } from '@tiendana/ngx-core/validators';
Dónde se Aplica y Dónde Queda el Error
- Validadores de grupo (
match, comparaciones, fechas): se registran en elFormGroup. Escriben su error en el control destino consetErrorsy devuelvennulldesde el grupo. El mensaje se muestra con el[tdnErrors]de ese control. - Validadores de control (
maxFileSize,minFileSize,fileType,verifyNIT) y asíncronos (requiredFileType,alreadyExist): se registran en el control.
En los validadores de comparación, si alguno de los dos campos está vacío (null, undefined, '', NaN o []; 0 sí cuenta como valor) el validador limpia su error y no valida.
Validadores de Comparación
match
Verifica que dos campos tengan el mismo valor (confirmación de contraseña o de correo). El error queda en duplicatePathField.
static match(originalPathField: string, duplicatePathField: string): ValidatorFn
| Parámetro | Descripción |
|---|---|
originalPathField | Ruta del campo original |
duplicatePathField | Ruta del campo que debe coincidir; recibe el error |
Error: { match: true }. Compara con === y no se salta los campos vacíos: dos campos vacíos coinciden.
Si el usuario corrige el campo original hasta igualarlo, el duplicado queda con errors = {} y Angular lo marca inválido sin mensaje que mostrar. Se corrige al volver a editar el duplicado. Valida también el duplicado (p. ej. updateValueAndValidity() en valueChanges del original) o usa un validador propio.
lowerThan
El valor de TargetPathField debe ser estrictamente menor que el de GreaterPathField.
static lowerThan(TargetPathField: string, GreaterPathField: string): ValidatorFn
Error en el campo objetivo: { lowerThan: true }
lowerOrEqualThan
El valor de TargetPathField debe ser menor o igual que el de GreaterPathField.
static lowerOrEqualThan(TargetPathField: string, GreaterPathField: string): ValidatorFn
Error: { lowerOrEqualThan: true }
greaterThan
El valor de TargetPathField debe ser estrictamente mayor que el de LowerPathField.
static greaterThan(TargetPathField: string, LowerPathField: string): ValidatorFn
Error: { greaterThan: true }
greaterOrEqualThan
El valor de TargetPathField debe ser mayor o igual que el de LowerPathField.
static greaterOrEqualThan(TargetPathField: string, LowerPathField: string): ValidatorFn
Error: { greaterOrEqualThan: true }
betweenRange
El valor de TargetPathField debe estar entre los de LowerPathField y GreaterPathField, ambos incluidos.
static betweenRange(
TargetPathField: string,
LowerPathField: string,
GreaterPathField: string
): ValidatorFn
Error: { betweenRange: true }
Validadores de Fechas
dateLowerThan
La fecha de TargetPathField (el campo que recibe el error) debe ser anterior a la del segundo campo.
static dateLowerThan(TargetPathField: string, LowerPathField: string): ValidatorFn
Error: { dateLowerThan: true }
dateGreaterThan
La fecha de TargetPathField (el campo que recibe el error) debe ser posterior a la del segundo campo.
static dateGreaterThan(TargetPathField: string, GreaterPathField: string): ValidatorFn
Error: { dateGreaterThan: true }. Con fechas iguales hay error: la comparación es estricta.
Los valores se convierten con new Date(...). A las cadenas que no contienen +, z ni Z se les añade una Z final antes de parsear, de modo que se interpretan como UTC. Una cadena con desfase negativo (-05:00) no cumple esa regla y se vuelve inválida al añadirle la Z: usa objetos Date, ISO con Z o con +.
En ambos validadores el primer argumento es el campo que recibe el error. El nombre del segundo parámetro en la firma (LowerPathField en dateLowerThan, GreaterPathField en dateGreaterThan) describe lo contrario de su papel real, así que guíate por la regla de arriba. Además, dateGreaterThan limpia la clave greaterThan (y no dateGreaterThan) cuando uno de los campos queda vacío, por lo que un error previo no se retira hasta que ambos campos tengan valor.
Validadores de Archivos
Validan el valor del control cuando es un File o un Blob; con cualquier otro valor (null, una URL) no hacen nada y el control es válido.
maxFileSize
El archivo debe ser menor que el límite.
static maxFileSize(size: string): ValidatorFn
| Parámetro | Descripción |
|---|---|
size | Límite con unidad: "5MB", "100KB", "1GB". Las unidades son decimales (1 KB = 1000 bytes). Sin unidad se interpretan bytes. |
Error: { maxFileSize: true } cuando el tamaño es mayor o igual al límite. Una cadena sin número registra un console.error y el control es válido.
file: [null, CustomValidators.maxFileSize('10MB')]
minFileSize
El archivo debe ser mayor que el límite.
static minFileSize(size: string): ValidatorFn
Error: { minFileSize: true } cuando el tamaño es menor o igual al límite.
fileType
Valida el tipo por la extensión del nombre. La extensión del archivo se pasa a minúsculas, pero la lista types no: escribe las extensiones en minúsculas ('pdf', no 'PDF'). Es rápido pero fácil de engañar.
static fileType(types: string | string[]): ValidatorFn
Error: { fileType: true }. Solo evalúa valores que sean File (un Blob sin nombre no se valida).
image: [null, CustomValidators.fileType(['jpg', 'jpeg', 'png', 'gif'])]
requiredFileType (Async)
Valida el tipo leyendo la cabecera binaria del archivo con getFileType. No depende de la extensión.
static requiredFileType(ext: string | string[]): AsyncValidatorFn
Error: { requiredFileType: true }
document: [null, {
asyncValidators: [CustomValidators.requiredFileType(['pdf', 'png'])]
}]
getFileType puede devolverSolo se reconocen png, gif, jpeg, pdf y xlsx. Observa que:
- JPEG se llama
jpeg:'jpg'nunca coincide. - Todo archivo que empiece como un ZIP (
.xlsx,.docx,.zip...) se detecta comoxlsx. svgyzipno se detectan nunca;requiredFileType('svg')yrequiredFileType('zip')rechazan siempre.
Además, getFileType escribe tres líneas en console.log en cada validación.
Validador Remoto
alreadyExist (Async)
Comprueba con una petición HTTP si un valor ya existe (correo, slug, documento).
static alreadyExist(
http: HttpClient,
url: string,
requestBody: BaseValidationModel
): AsyncValidatorFn
| Parámetro | Descripción |
|---|---|
http | Instancia de HttpClient |
url | Endpoint de validación, que recibe un POST |
requestBody | { Model, Field, Id? }; Value lo asigna el validador con el valor del control |
interface BaseValidationModel {
Model: string; // Entidad o tabla (ej: 'User')
Field: string; // Campo a comprobar (ej: 'email')
Id?: string | number | null; // Id a excluir al editar
Value?: any | null; // Se asigna automáticamente
}
Este tipo no se exporta del paquete: escribe el objeto en línea.
Error: { alreadyExist: true }
sku: ['', {
validators: [Validators.required],
asyncValidators: [
CustomValidators.alreadyExist(this.http, '/api/validate', { Model: 'Product', Field: 'sku', Id: null })
]
}]
- Una respuesta 2xx (cualquiera) significa que el valor ya existe: error
alreadyExist. - Un error HTTP (404, 500, red caída...) se trata como "no existe" y el control es válido.
- Un valor vacío no hace petición y es válido.
Diseña el endpoint para responder 404 cuando el valor está disponible. Como cualquier fallo se interpreta como válido, no lo uses como única barrera de unicidad: el servidor debe volver a comprobarlo al guardar.
Cada validación espera 1 segundo antes de pedir. El validador escribe Value sobre el mismo requestBody que le pasaste, así que no reutilices un mismo objeto en dos controles.
Validador Específico
verifyNIT
Valida un NIT colombiano con su dígito de verificación (formato XXXXXXXXX-V). Se pasa la referencia, no se invoca.
static verifyNIT(control: AbstractControl): { [key: string]: any } | null
Errores:
{ verifyNIT: true }: el formato no coincide condígitos-dígito{ verifyNITIntegrity: true }: el dígito de verificación no corresponde
nit: ['', CustomValidators.verifyNIT]
El dígito se calcula con los primeros nueve dígitos del NIT (pesos 41, 37, 29, 23, 19, 17, 13, 7 y 3, módulo 11). Por eso un NIT con entre uno y ocho dígitos falla con verifyNITIntegrity; uno sin dígitos antes del guion (-5) se acepta como válido, así que combínalo con Validators.required y un Validators.pattern si necesitas exigir la longitud. Los dígitos posteriores al noveno se ignoran. Un valor vacío es válido: combínalo con Validators.required.
Para calcular el dígito de verificación de un NIT, consulta calculateDv en @tiendana/utils.
Ejemplos y Demos
Los mensajes se muestran con el [tdnErrors] del control que recibe el error. Ver Manejo de Errores.
Validación match (Contraseñas)
Ver código
Validación de Rango Numérico
Ver código
Validación de Fechas
Ver código
Validación de Archivo
Ver código
Validación Remota (alreadyExist)
Ver código
La demo de alreadyExist usa un endpoint simulado. En producción, el backend debe responder 2xx si el valor existe y un error HTTP (por ejemplo 404) si está disponible.
Notas Importantes
- Validadores de grupo:
match, comparaciones y fechas se registran en elFormGroup; el error aparece en el control destino. - Validadores de control: archivos y NIT se registran en el control directamente.
- Asíncronos:
alreadyExistyrequiredFileType; van enasyncValidators. - Cabecera, no extensión:
requiredFileTypelee los bytes del archivo;fileTypesolo mira el nombre.
Ver También
- Array Validators: validadores para longitud de arrays
- File Read Utilities: utilidades de lectura de archivos
- Manejo de Errores: mostrar los errores en pantalla
- TdnInputFile: componente para selección de archivos