Validación de formularios

Intermedio
Angular
Angular
Actualizado: 24/09/2025

Validaciones básicas

Los formularios template-driven de Angular proporcionan un sistema de validación integrado que aprovecha los atributos HTML5 estándar para establecer reglas de validación directamente en el template. Esta aproximación resulta intuitiva para desarrolladores familiarizados con HTML, ya que utiliza atributos nativos del navegador combinados con la funcionalidad de Angular.

Atributos HTML5 de validación

Angular reconoce automáticamente los atributos de validación HTML5 cuando se utilizan en elementos que tienen la directiva ngModel. Los validadores más comunes incluyen:

Validación de campos obligatorios:

<input 
  type="text" 
  name="nombre" 
  [(ngModel)]="usuario.nombre"
  required
  #nombre="ngModel">

Validación de longitud mínima y máxima:

<input 
  type="password" 
  name="password" 
  [(ngModel)]="usuario.password"
  required
  minlength="8"
  maxlength="20"
  #password="ngModel">

Validación de formato de email:

<input 
  type="email" 
  name="email" 
  [(ngModel)]="usuario.email"
  required
  email
  #email="ngModel">

Validación con patrones personalizados:

<input 
  type="text" 
  name="telefono" 
  [(ngModel)]="usuario.telefono"
  pattern="[0-9]{9}"
  #telefono="ngModel"
  placeholder="Ejemplo: 123456789">

Variables de plantilla para validación

La sintaxis de variable de plantilla #campo="ngModel" es fundamental para acceder al estado de validación de cada campo. Esta variable expone propiedades que permiten conocer el estado actual del input:

<form #formularioUsuario="ngForm">
  <input 
    type="text" 
    name="username" 
    [(ngModel)]="usuario.username"
    required
    minlength="3"
    #username="ngModel">
  
  <input 
    type="email" 
    name="email" 
    [(ngModel)]="usuario.email"
    required
    email
    #email="ngModel">
</form>

Estados de validación

Cada campo con ngModel mantiene varios estados de validación que se actualizan automáticamente según las interacciones del usuario:

Estados de validez:

  • valid: El campo cumple todas las reglas de validación
  • invalid: El campo no cumple una o más reglas de validación
  • errors: Objeto que contiene los errores específicos del campo

Estados de interacción:

  • touched: El usuario ha enfocado y luego salido del campo
  • untouched: El campo no ha recibido foco aún
  • pristine: El valor del campo no ha sido modificado
  • dirty: El valor del campo ha sido modificado

Ejemplo de acceso a estados:

<form #formulario="ngForm">
  <input 
    type="text" 
    name="nombre" 
    [(ngModel)]="usuario.nombre"
    required
    minlength="2"
    #nombre="ngModel">
  
  <!-- Mostrar información de estado (solo para debug) -->
  <div>
    <p>Válido: {{ nombre.valid }}</p>
    <p>Tocado: {{ nombre.touched }}</p>
    <p>Pristine: {{ nombre.pristine }}</p>
    <p>Errores: {{ nombre.errors | json }}</p>
  </div>
</form>

Objeto errors

El objeto errors contiene información detallada sobre qué validaciones específicas han fallado. Cada validador que falla añade una propiedad al objeto:

// Ejemplo de objeto errors para un campo inválido:
{
  required: true,           // Campo obligatorio vacío
  minlength: {              // Longitud mínima no alcanzada
    requiredLength: 8,
    actualLength: 5
  },
  email: true              // Formato de email inválido
}

Acceso a errores específicos en el template:

<input 
  type="email" 
  name="correo" 
  [(ngModel)]="usuario.correo"
  required
  email
  minlength="5"
  #correo="ngModel">

<!-- Verificar errores específicos -->
<div>
  <p>¿Campo requerido? {{ correo.errors?.['required'] }}</p>
  <p>¿Email inválido? {{ correo.errors?.['email'] }}</p>
  <p>¿Muy corto? {{ correo.errors?.['minlength'] }}</p>
</div>

Validación de formulario completo

El formulario como conjunto también mantiene su propio estado de validación, que se deriva del estado de todos sus campos:

<form #miFormulario="ngForm">
  <input 
    type="text" 
    name="nombre" 
    [(ngModel)]="datos.nombre"
    required
    #nombre="ngModel">
  
  <input 
    type="email" 
    name="email" 
    [(ngModel)]="datos.email"
    required
    email
    #email="ngModel">
  
  <!-- Estado del formulario completo -->
  <div>
    <p>Formulario válido: {{ miFormulario.valid }}</p>
    <p>Formulario tocado: {{ miFormulario.touched }}</p>
    <p>Formulario pristine: {{ miFormulario.pristine }}</p>
  </div>
</form>

La validación se ejecuta automáticamente cada vez que el usuario modifica el valor de un campo, y Angular actualiza todos los estados correspondientes. Esta reactividad permite crear interfaces de usuario que respondan inmediatamente a las acciones del usuario, proporcionando retroalimentación en tiempo real sobre la validez de los datos introducidos.

Mensajes de error

Una vez establecidas las reglas de validación, el siguiente paso es mostrar mensajes de error claros y útiles al usuario. Angular proporciona un sistema flexible para mostrar mensajes condicionalmente basándose en el estado de validación de cada campo, utilizando la nueva sintaxis de control de flujo @if para crear una experiencia de usuario intuitiva.

Mensajes condicionales con @if

La sintaxis @if de Angular permite mostrar mensajes de error únicamente cuando se cumplen ciertas condiciones. La estrategia más común es mostrar errores solo después de que el usuario haya interactuado con el campo:

<form #contactoForm="ngForm">
  <div class="campo-grupo">
    <label for="nombre">Nombre completo</label>
    <input 
      type="text" 
      id="nombre"
      name="nombre" 
      [(ngModel)]="contacto.nombre"
      required
      minlength="2"
      #nombre="ngModel"
      class="form-control">
    
    @if (nombre.invalid && nombre.touched) {
      <div class="mensajes-error">
        @if (nombre.errors?.['required']) {
          <small class="error">El nombre es obligatorio</small>
        }
        @if (nombre.errors?.['minlength']) {
          <small class="error">El nombre debe tener al menos 2 caracteres</small>
        }
      </div>
    }
  </div>
</form>

Estrategias para mostrar errores

Existen diferentes momentos apropiados para mostrar mensajes de error según la experiencia de usuario deseada:

Mostrar errores después de tocar el campo:

<input 
  type="email" 
  name="email" 
  [(ngModel)]="usuario.email"
  required
  email
  #email="ngModel">

@if (email.invalid && email.touched) {
  <div class="error-container">
    @if (email.errors?.['required']) {
      <span class="mensaje-error">El email es obligatorio</span>
    }
    @if (email.errors?.['email']) {
      <span class="mensaje-error">Introduce un email válido</span>
    }
  </div>
}

Mostrar errores después de modificar el campo:

<input 
  type="password" 
  name="password" 
  [(ngModel)]="usuario.password"
  required
  minlength="8"
  maxlength="20"
  #password="ngModel">

@if (password.invalid && password.dirty) {
  <div class="alertas">
    @if (password.errors?.['required']) {
      <p class="alerta-error">La contraseña es obligatoria</p>
    }
    @if (password.errors?.['minlength']) {
      <p class="alerta-error">
        La contraseña debe tener al menos 8 caracteres
        (actual: {{ password.errors?.['minlength'].actualLength }})
      </p>
    }
  </div>
}

Mensajes de error específicos por validador

Cada tipo de validación puede tener su mensaje personalizado que proporcione información clara sobre cómo corregir el error:

<div class="formulario-registro">
  <input 
    type="text" 
    name="telefono" 
    [(ngModel)]="usuario.telefono"
    required
    pattern="[0-9]{9}"
    #telefono="ngModel"
    placeholder="123456789">
  
  @if (telefono.invalid && telefono.touched) {
    <div class="errores-validacion">
      @if (telefono.errors?.['required']) {
        <span class="error-requerido">El teléfono es obligatorio</span>
      }
      @if (telefono.errors?.['pattern']) {
        <span class="error-formato">
          El teléfono debe contener exactamente 9 dígitos
        </span>
      }
    </div>
  }
</div>

Ejemplo con validación de longitud detallada:

<textarea 
  name="comentario" 
  [(ngModel)]="feedback.comentario"
  required
  minlength="10"
  maxlength="500"
  #comentario="ngModel"
  rows="4">
</textarea>

@if (comentario.invalid && comentario.dirty) {
  <div class="feedback-errores">
    @if (comentario.errors?.['required']) {
      <div class="error">Por favor, escribe un comentario</div>
    }
    @if (comentario.errors?.['minlength']) {
      <div class="error">
        El comentario debe tener al menos 10 caracteres
        (faltan {{ comentario.errors?.['minlength'].requiredLength - comentario.errors?.['minlength'].actualLength }})
      </div>
    }
    @if (comentario.errors?.['maxlength']) {
      <div class="error">
        El comentario excede el límite de 500 caracteres
        (sobran {{ comentario.errors?.['maxlength'].actualLength - comentario.errors?.['maxlength'].requiredLength }})
      </div>
    }
  </div>
}

Estilos CSS para campos inválidos

Angular añade automáticamente clases CSS a los campos según su estado de validación. Estas clases permiten aplicar estilos visuales que refuercen el feedback de validación:

/* Estilos para campos válidos e inválidos */
.form-control.ng-valid.ng-touched {
  border-color: #28a745;
  box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
}

.form-control.ng-invalid.ng-touched {
  border-color: #dc3545;
  box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
}

/* Estilos para mensajes de error */
.mensajes-error {
  margin-top: 0.25rem;
}

.mensaje-error {
  display: block;
  color: #dc3545;
  font-size: 0.875rem;
  margin-top: 0.25rem;
}

.error-container {
  background-color: #f8d7da;
  border: 1px solid #f5c6cb;
  border-radius: 0.25rem;
  padding: 0.5rem;
  margin-top: 0.5rem;
}

Ejemplo completo con estilos integrados:

<form #perfilForm="ngForm" class="formulario-perfil">
  <div class="grupo-campo">
    <label for="username">Nombre de usuario</label>
    <input 
      type="text" 
      id="username"
      name="username" 
      [(ngModel)]="perfil.username"
      required
      minlength="3"
      maxlength="15"
      pattern="[a-zA-Z0-9_]+"
      #username="ngModel"
      class="form-control"
      [class.campo-invalido]="username.invalid && username.touched">
    
    @if (username.invalid && username.touched) {
      <div class="contenedor-errores">
        @if (username.errors?.['required']) {
          <div class="mensaje-error">
            <i class="icon-warning"></i>
            El nombre de usuario es obligatorio
          </div>
        }
        @if (username.errors?.['minlength']) {
          <div class="mensaje-error">
            <i class="icon-warning"></i>
            Debe tener al menos 3 caracteres
          </div>
        }
        @if (username.errors?.['maxlength']) {
          <div class="mensaje-error">
            <i class="icon-warning"></i>
            No puede exceder 15 caracteres
          </div>
        }
        @if (username.errors?.['pattern']) {
          <div class="mensaje-error">
            <i class="icon-warning"></i>
            Solo se permiten letras, números y guiones bajos
          </div>
        }
      </div>
    }
  </div>
</form>

Mensajes de estado del formulario

Además de los mensajes por campo, es útil mostrar mensajes generales sobre el estado del formulario completo:

<form #registroForm="ngForm">
  <!-- Campos del formulario... -->
  
  @if (registroForm.invalid && registroForm.touched) {
    <div class="alerta-formulario">
      <h4>Por favor, corrige los siguientes errores:</h4>
      <ul>
        @if (nombre.invalid) {
          <li>Completa el campo nombre correctamente</li>
        }
        @if (email.invalid) {
          <li>Introduce un email válido</li>
        }
        @if (password.invalid) {
          <li>La contraseña debe cumplir los requisitos</li>
        }
      </ul>
    </div>
  }
  
  <button 
    type="submit" 
    [disabled]="registroForm.invalid"
    class="btn-submit">
    Registrarse
  </button>
</form>

La combinación de mensajes específicos por campo y feedback visual a través de CSS proporciona una experiencia de usuario clara y profesional. Los usuarios reciben información inmediata sobre qué necesita ser corregido y cómo hacerlo, lo que reduce la frustración y mejora la tasa de completación de formularios.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Angular

Documentación oficial de Angular
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Angular

Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender cómo Angular integra validaciones básicas usando atributos HTML5 en formularios template-driven.
  • Aprender a utilizar variables de plantilla para acceder a estados de validación de campos.
  • Identificar y manejar los diferentes estados de validación y errores específicos de cada campo.
  • Implementar mensajes de error condicionales para mejorar la experiencia del usuario.
  • Aplicar estilos CSS para reflejar visualmente el estado de validación de los campos y formularios.