Saltar al contenido principal

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 el FormGroup. Escriben su error en el control destino con setErrors y devuelven null desde 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ámetroDescripción
originalPathFieldRuta del campo original
duplicatePathFieldRuta 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.

Duplicado inválido sin mensaje

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.

Coerción de fechas

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 +.

Detalles de los comparadores de fecha

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ámetroDescripción
sizeLí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'])]
}]
Tipos que getFileType puede devolver

Solo 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 como xlsx.
  • svg y zip no se detectan nunca; requiredFileType('svg') y requiredFileType('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ámetroDescripción
httpInstancia de HttpClient
urlEndpoint 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 })
]
}]
Contrato de la respuesta
  • 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.

Debounce y objeto compartido

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 con dí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)​

Cargando demo...
Ver código

Validación de Rango Numérico​

Cargando demo...
Ver código

Validación de Fechas​

Cargando demo...
Ver código

Validación de Archivo​

Cargando demo...
Ver código

Validación Remota (alreadyExist)​

Cargando demo...
Ver código
info

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 el FormGroup; el error aparece en el control destino.
  • Validadores de control: archivos y NIT se registran en el control directamente.
  • Asíncronos: alreadyExist y requiredFileType; van en asyncValidators.
  • Cabecera, no extensión: requiredFileType lee los bytes del archivo; fileType solo mira el nombre.

Ver También​