Formularios reactivos en Angular

Avanzado
Angular
Angular
Actualizado: 27/08/2024

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Los formularios reactivos en Angular, también conocidos como Reactive Forms, son una forma de manejar formularios de manera más programática y declarativa en comparación con los formularios basados en plantillas (Template-driven Forms). Se trata de una técnica para desarrollar formularios donde la lógica de validación y gestión de los datos se realiza en el controlador en lugar de en la plantilla.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Los formularios reactivos se construyen utilizando FormControl, FormGroup y FormArray a través del módulo ReactiveFormsModule proporcionado por Angular. Estos formularios se definen completamente en el TypeScript del componente, permitiendo mayor flexibilidad y control sobre la validación, datos y estado del formulario.

Características principales

  • Declaración en TypeScript: Los formularios se crean y manejan en los archivos TypeScript del componente. Esto facilita la aplicación de lógica compleja y la reutilización del código.
  • Estructura inmutable: Cualquier cambio en el formulario produce una nueva instancia del mismo, lo que mejora la trazabilidad y el manejo de versiones.
  • Validación a nivel de modelo: La validación de los datos se realiza con las APIs de Angular en el controlador, permitiendo definir reglas de validación de una manera más estructurada y robusta.
  • Observables: Los cambios en los valores del formulario y de su estado se pueden observar utilizando Observables, lo que integra perfectamente con el sistema de reactividad de Angular basado en RxJS.

Conceptos básicos

Los formularios reactivos en Angular están basados en tres clases principales: FormControl, FormGroup y FormArray. Estas clases permiten construir y gestionar formularios de una manera declarativa, facilitando la validación y la manipulación dinámica de los formularios.

FormControl

FormControl representa un único campo de entrada en el formulario. Se utiliza para rastrear el valor, el estado y la validación de un control de formulario individual. Un FormControl se puede instanciar con un valor inicial, un array de validadores síncronos y un array de validadores asíncronos.

Ejemplo:

import { FormControl } from '@angular/forms';

const nameControl = new FormControl('initial value', [Validators.required, Validators.minLength(3)]);

FormGroup

FormGroup es una colección de FormControl u otros FormGroup, permitiendo anidar conjuntos de controles en estructuras jerárquicas. Es útil para representar formularios complejos y facilita la agrupación y validación conjunta de varios controles.

Ejemplo:

import { FormGroup, FormControl, Validators } from '@angular/forms';

const userProfileForm = new FormGroup({
  firstName: new FormControl('', [Validators.required]),
  lastName: new FormControl(''),
  email: new FormControl('', [Validators.email]),
});

FormArray

FormArray es una colección ordenada de FormControl, FormGroup o incluso otros FormArray. Este componente es útil cuando se debe manejar un conjunto dinámico de controles, como una lista de elementos con posibilidad de agregar o quitar elementos de forma dinámica.

Ejemplo:

import { FormArray, FormControl, FormGroup, Validators } from '@angular/forms';

const addressFormArray = new FormArray([
  new FormGroup({
    street: new FormControl('', Validators.required),
    city: new FormControl('', Validators.required)
  })
]);

addressFormArray.push(new FormGroup({
  street: new FormControl(''),
  city: new FormControl('')
}));

FormBuilder

FormBuilder es un servicio proporcionado por Angular que simplifica la creación de formularios reactivos complejos. Este servicio ofrece métodos convenientes para crear instancias de FormControl, FormGroup y FormArray, lo que resulta en un código más limpio y fácil de leer, especialmente para formularios grandes.

Para utilizar FormBuilder, primero debes inyectarlo en el constructor de tu componente:

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
 selector: 'app-profile-form',
 templateUrl: './profile-form.component.html'
})
export class ProfileFormComponent {
 profileForm: FormGroup;
 constructor(private fb: FormBuilder) {
   this.createForm();
 }
 createForm() {
   this.profileForm = this.fb.group({
     firstName: ['', Validators.required],
     lastName: [''],
     email: ['', [Validators.required, Validators.email]],
     address: this.fb.group({
       street: [''],
       city: [''],
       state: [''],
       zip: ['']
     }),
     aliases: this.fb.array([
       this.fb.control('')
     ])
   });
 }
}

En este ejemplo, fb.group() crea un FormGroup, fb.control() crea un FormControl, y fb.array() crea un FormArray.

FormBuilder es especialmente útil cuando trabajas con formularios grandes o complejos, ya que reduce significativamente la cantidad de código repetitivo y mejora la legibilidad.

Ejemplo de cómo añadir dinámicamente controles a un FormArray usando FormBuilder:

addAlias() {
 const aliases = this.profileForm.get('aliases') as FormArray;
 aliases.push(this.fb.control(''));
}

En este caso, fb.control('') crea un nuevo FormControl y lo añade al FormArray de aliases.

Manejo del estado y valores

Cada uno de estos componentes (FormControl, FormGroup, FormArray) tiene propiedades y métodos similares para manejar sus estados y valores. Las propiedades más comunes incluyen:

  • value: El valor actual del control o grupo.
  • status: El estado de validación actual, VALID, INVALID, PENDING o DISABLED.
  • errors: Los errores actuales si el control es inválido.
  • touched: Indicador de si el control ha sido tocado por el usuario.
  • untouched: Indicador de si el control no ha sido tocado por el usuario.
  • pristine: Indicador de si el control no ha sido modificado.
  • dirty: Indicador de si el control ha sido modificado.

Métodos comunes incluyen:

  • setValue: Establece un nuevo valor para el control, grupo o array.
  • patchValue: Similar a setValue, pero permite establecer valores de forma parcial.
  • reset: Reinicia el control al estado inicial.
  • disable: Desactiva el control.
  • enable: Activa el control.

Ejemplo:

// Acceder y modificar el valor de un control
userProfileForm.get('firstName')?.setValue('John');

// Comprobar el estado
if (userProfileForm.valid) {
  // Procesar los datos del formulario
}

// Resetear el formulario
userProfileForm.reset();

Validadores

En Angular, la validación de formularios reactivos es esencial para asegurar la integridad de los datos ingresados por los usuarios. Los formularios reactivos proporcionan una manera flexible y poderosa de definir y manejar las reglas de validación directamente en el componente. A continuación, se detalla cómo implementar validaciones en formularios reactivos.

Validadores predefinidos

Angular ofrece una serie de validadores integrados que puedes utilizar para reglas de validación comunes. Estos validadores están disponibles a través del módulo Validators.

Ejemplos de validadores integrados:

  • Validators.required: Verifica que el campo no esté vacío.
  • Validators.email: Valida que el valor sea un email en formato correcto.
  • Validators.minLength(4): Verifica que el campo tenga al menos 4 caracteres.
  • Validators.maxLength(10): Verifica que el campo no tenga más de 10 caracteres.
  • Validators.pattern('^[a-zA-Z]+$'): Valida que el valor siga un patrón específico.

Cómo usar validadores integrados:

import { FormControl, Validators } from '@angular/forms';

const nameControl = new FormControl('', [Validators.required, Validators.minLength(4)]);
const emailControl = new FormControl('', [Validators.required, Validators.email]);
const passwordControl = new FormControl('', [Validators.required, Validators.pattern('^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$')]);

Validadores personalizados

A veces, los validadores integrados no son suficientes para las necesidades específicas de una aplicación. En estos casos, se pueden construir validadores personalizados.

Un validador personalizado es una función que recibe un AbstractControl y devuelve un objeto de errores o null si la validación es exitosa.

Ejemplo de validador personalizado para un nombre prohibido:

import { AbstractControl, ValidationErrors } from '@angular/forms';

function forbiddenNameValidator(control: AbstractControl): ValidationErrors | null {
  const forbidden = /forbiddenName/.test(control.value);
  return forbidden ? { forbiddenName: { value: control.value } } : null;
}

const usernameControl = new FormControl('', [forbiddenNameValidator]);

Validadores asíncronos

Los validadores asíncronos se utilizan cuando la validación depende de una operación asíncrona, como una llamada a un servidor. Un validador asíncrono devuelve un Observable que resuelve los errores o null.

Ejemplo de validador asíncrono para verificar la disponibilidad del nombre de usuario:

import { FormControl, AsyncValidatorFn } from '@angular/forms';
import { Observable, of, map, delay } from 'rxjs';

  usernameTakenValidator(): AsyncValidatorFn {
    return (control: AbstractControl): Observable<ValidationErrors | null> => {
      return of(control.value === 'taken').pipe(
        delay(1000),
        map(isTaken => (isTaken ? { usernameTaken: true } : null))
      );
    };
  }

const asyncUsernameControl = new FormControl('', [], [usernameTakenValidator]);

Mensajes de error

Para proporcionar feedback útil al usuario, se pueden mostrar mensajes de error en la plantilla HTML. Usando las nuevas estructuras de control @if, se pueden mostrar mensajes específicos para cada tipo de error.

Ejemplo de muestra de mensajes de error:

<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  
  <label for="name">Nombre:</label>
  <input id="name" formControlName="name">
  @if (nameControl?.invalid && nameControl?.touched) {
    @if (nameControl?.hasError('required')) {
      <small>El nombre es obligatorio.</small>
    }
    @if (nameControl?.hasError('minlength')) {
      <small>El nombre debe tener al menos 4 caracteres.</small>
    }
  }
  
  <label for="email">Email:</label>
  <input id="email" formControlName="email">
  @if (emailControl?.invalid && emailControl?.touched) {
    @if (emailControl?.hasError('required')) {
      <small>El email es obligatorio.</small>
    }
    @if (emailControl?.hasError('email')) {
      <small>Introduce un email válido.</small>
    }
  }

  <button type="submit" [disabled]="registerForm.invalid">Registrar</button>
</form>

Este sería el componente Typescript:

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';

@Component({
  selector: 'app-register-form',
  standalone: true,
  imports: [FormsModule, CommonModule, ReactiveFormsModule],
  templateUrl: './register-form.component.html',
  styleUrls: ['./register-form.component.css']
})
export class RegisterFormComponent implements OnInit {
  registerForm!: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.registerForm = this.fb.group({
      name: ['', [Validators.required, Validators.minLength(4)]],
      email: ['', [Validators.required, Validators.email]]
    });
  }

  onSubmit() {
    if (this.registerForm.valid) {
      console.log('Formulario enviado', this.registerForm.value);
      // Aquí iría la lógica para enviar los datos al servidor
    } else {
      console.log('Formulario inválido');
      // Marcar todos los campos como tocados para mostrar los errores
      Object.values(this.registerForm.controls).forEach(control => {
        control.markAsTouched();
      });
    }
  }

  // Métodos de utilidad para acceder a los controles del formulario desde la plantilla
  get nameControl() {
    return this.registerForm.get('name');
  }

  get emailControl() {
    return this.registerForm.get('email');
  }
}

Así se vería este ejemplo en el navegador, sin hacer cambios en los estilos:

Manejadores de validación en FormGroup

Puedes también definir validaciones a nivel de FormGroup para validaciones cruzadas entre múltiples controles.

Ejemplo de validador de coincidencia de contraseñas:

import { FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';

const passwordMatchValidator: ValidatorFn = (group: FormGroup): ValidationErrors | null => {
  const password = group.get('password');
  const confirmPassword = group.get('confirmPassword');
  return password && confirmPassword && password.value !== confirmPassword.value ? { passwordMismatch: true } : null;
};

this.registerForm = new FormGroup({
  password: new FormControl(''),
  confirmPassword: new FormControl(''),
}, { validators: passwordMatchValidator });

Este enfoque asegura que tanto controladores individuales como grupos de control puedan ser validados de manera eficiente siguiendo las necesidades específicas del negocio.

Pasos para crear un formulario reactivo

  1.- Importar ReactiveFormsModule

Asegúrate de importar el módulo ReactiveFormsModule de @angular/forms en el módulo principal (app.module.ts).

// app.module.ts

import { ReactiveFormsModule } from '@angular/forms';
// ...

@NgModule({
    imports: [
        BrowserModule,
        ReactiveFormsModule // Añadir ReactiveFormsModule aquí
    ],
    // ...
})
export class AppModule { }

En caso de estar en un componente standalone true entonces importarlo directamente sobre el componente:

  2.- Crear un componente para el formulario

Genera un nuevo componente para tu formulario llamado mi-formulario-reactivo utilizando Angular CLI.

ng generate component mi-formulario-reactivo

  3.- Importar las clases necesarias en el componente

En tu componente (mi-formulario-reactivo.component.ts), importa FormGroup y FormControl desde @angular/forms.

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
selector: 'app-mi-formulario-reactivo',
templateUrl: './mi-formulario-reactivo.component.html',
styleUrls: ['./mi-formulario-reactivo.component.css']
})
export class MiFormularioReactivoComponent {
// ...
}

  4.- Definir la estructura del formulario en el componente

Utiliza instancias de FormGroup y FormControl para definir la estructura del formulario. Este modelo de formulario se vinculará más adelante con la plantilla HTML.

export class MiFormularioReactivoComponent {
  registerForm = new FormGroup({
    name: new FormControl(''),
    email: new FormControl(''),
  });

  onSubmit() {
    console.log(this.registerForm.value);
  }
}

  5.- Vincular los elementos del formulario a las instancias de las clases en la plantilla del componente

En la plantilla de tu componente (mi-formulario-reactivo.component.html), utiliza el atributo formGroup para vincular el formulario y formControlName para vincular cada control de formulario.

<form [formGroup]="registerForm" (ngSubmit)="onSubmit()">
  <label for="name">Nombre:</label>
  <input id="name" formControlName="name">
  
  <label for="email">Email:</label>
  <input id="email" formControlName="email">
  
  <button type="submit">Registrar</button>
</form>

Ejemplo:

  6.- Manejar el envío del formulario

Implementa la función onSubmit() en tu componente para manejar el evento de envío del formulario.  Aquí puedes, por ejemplo, enviar los datos a un servidor o validarlos antes de la transmisión.

export class MiFormularioReactivoComponent {
    // ...
  onSubmit() {
    if (this.registerForm.valid) {
      console.log(this.registerForm.value);
    }
  }
}

Ejemplo de función save que captura los datos del formulario a un objeto Book y lo envía a backend:

Observar cambios

En los formularios reactivos de Angular, el observar cambios en los valores y estados de los controles es fundamental para crear aplicaciones dinámicas y reactivas. Esto se realiza mediante el uso de observables que son emitidos por los controles de formulario (FormControl), grupos de formulario (FormGroup) y arrays de formulario (FormArray). Angular, al estar profundamente integrado con RxJS, ofrece una API sencilla para ello.

this.formulario.valueChanges.subscribe(cambios => {
  console.log(cambios);
});

Observables de cambio de valor

Cada instancia de FormControl, FormGroup y FormArray proporciona un observable de su valor (valueChanges) que se puede suscribir para recibir notificaciones cada vez que el valor cambia.

Ejemplo de uso de valueChanges en un FormControl:

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-login-form',
  templateUrl: './login-form.component.html'
})
export class LoginFormComponent {
  username = new FormControl('');

  constructor() {
    this.username.valueChanges.subscribe(value => {
      console.log('Username changed to:', value);
    });
  }
}

Observables de cambio de estado

Los controles de formulario también proporcionan un observable para los cambios en su estado (statusChanges). Este observable es útil para monitorizar el estado de validación del control.

Ejemplo de uso de statusChanges en un FormGroup:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-auth-form',
  templateUrl: './auth-form.component.html'
})
export class AuthFormComponent {
  authForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', [Validators.required, Validators.minLength(6)])
  });

  constructor() {
    this.authForm.statusChanges.subscribe(status => {
      console.log('Form status changed to:', status);
    });
  }
}

Observación conjunta de valores o estados

Para observar los cambios en múltiples controles de formulario, se puede utilizar combinación de observables (combineLatest, merge, etc.) proporcionada por RxJS.

Ejemplo de observación conjunta de valores:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { combineLatest } from 'rxjs';

@Component({
  selector: 'app-multi-field-form',
  templateUrl: './multi-field-form.component.html'
})
export class MultiFieldFormComponent {
  multiFieldForm = new FormGroup({
    field1: new FormControl('', Validators.required),
    field2: new FormControl('', Validators.required)
  });

  constructor() {
    const field1$ = this.multiFieldForm.get('field1').valueChanges;
    const field2$ = this.multiFieldForm.get('field2').valueChanges;

    combineLatest([field1$, field2$]).subscribe(([field1Value, field2Value]) => {
      console.log('Field1:', field1Value, ' Field2:', field2Value);
    });
  }
}

Utilización práctica en formularios avanzados

Observar cambios es especialmente útil para casos más avanzados como formularios dependientes dónde, por ejemplo, el valor de un campo depende del valor de otro. Este patrón es comúnmente usado para mostrar mensajes de error en tiempo real, habilitar/deshabilitar botones de formulario, o modificar la UI dinámicamente.

Ejemplo de uso práctico en un formulario dependiente:

import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-dependent-form',
  templateUrl: './dependent-form.component.html'
})
export class DependentFormComponent {
  dependentForm = new FormGroup({
    country: new FormControl(''),
    state: new FormControl({ value: '', disabled: true })
  });

  constructor() {
    this.dependentForm.get('country').valueChanges.subscribe(selectedCountry => {
      const stateControl = this.dependentForm.get('state');
      if (selectedCountry === 'USA') {
        stateControl.enable();
        stateControl.setValidators(Validators.required);
      } else {
        stateControl.disable();
        stateControl.clearValidators();
      }
      stateControl.updateValueAndValidity();
    });
  }
}

Este enfoque proporciona una experiencia de usuario enriquecida y mejora la interactividad de la aplicación utilizando las capacidades reactivas de Angular y RxJS.

Aprendizajes de esta lección

  1. Comprender el concepto de formularios reactivos en Angular.
  2. Crear y gestionar formularios usando FormControl, FormGroup y FormArray.
  3. Implementar validaciones síncronas y asíncronas.
  4. Utilizar observables para rastrear y reaccionar a cambios en los formularios.
  5.  Aprovechar FormBuilder para simplificar la construcción de formularios complejos.
  6. Manejar el estado y los valores del formulario de manera eficiente.

Completa Angular y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración