Angular

Angular

Tutorial Angular: Formularios reactivos en Angular

Angular formularios reactivos: guía completa. Aprende a crear formularios reactivos en Angular con ejemplos prácticos y detallados.

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.

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.

Certifícate en Angular con CertiDevs PLUS

Ejercicios de esta lección Formularios reactivos en Angular

Evalúa tus conocimientos de esta lección Formularios reactivos en Angular con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.

Signals en Angular

Angular
Puzzle

Guards funcionales

Angular
Test

Decodificar JWT en Angular

Angular
Test

Servicio con HttpClient

Angular
Código

Ciclo de vida de componentes en Angular

Angular
Test

Gestión de productos de Fake Store API

Angular
Proyecto

Data binding en Angular

Angular
Test

Routes sin módulos en Angular

Angular
Código

Router en Angular

Angular
Test

Instalación de Angular

Angular
Puzzle

Route Guards basados en interfaces

Angular
Código

La directiva @if en Angular

Angular
Puzzle

Formularios reactivos en Angular

Angular
Código

Servicios en Angular

Angular
Puzzle

Interceptor funcional

Angular
Test

Servicio con Array

Angular
Código

La directiva @for en Angular

Angular
Puzzle

Interceptores HTTP

Angular
Código

Componentes standalone true

Angular
Puzzle

Formularios con ngModel en Angular

Angular
Puzzle

Routes en Angular

Angular
Test

Comunicación entre componentes Angular

Angular
Test

Parámetros en rutas con ActivatedRoute

Angular
Test

CRUD de Restaurantes y Platos

Angular
Proyecto

Tablas en Angular Material

Angular
Puzzle

Formulario de registro de usuarios

Angular
Proyecto

Instalación y uso de NgBoostrap

Angular
Puzzle

Desarrollo de componentes Angular

Angular
Test

JWT en Angular

Angular
Código

Formularios reactivos en Angular

Angular
Puzzle

Formularios en Angular Material

Angular
Puzzle

Layout con Angular Material

Angular
Puzzle

Effects en Angular

Angular
Test

Data binding

Angular
Código

HttpClient en servicios de Angular

Angular
Puzzle

Desarrollo de módulos Angular

Angular
Puzzle

Comandos Angular CLI

Angular
Puzzle

Subir archivo en formularios

Angular
Test

La directiva routerLink en Angular

Angular
Test

Todas las lecciones de Angular

Accede a todas las lecciones de Angular y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Instalación Angular

Angular

Introducción Y Entorno

Comandos Angular Cli

Angular

Introducción Y Entorno

Desarrollo De Componentes Angular

Angular

Componentes

Data Binding En Angular

Angular

Componentes

Ciclo De Vida De Componentes En Angular

Angular

Componentes

Comunicación Entre Componentes Angular

Angular

Componentes

La Directiva @If En Angular

Angular

Componentes

La Directiva @For En Angular

Angular

Componentes

Componentes Standalone

Angular

Componentes

Desarrollo De Módulos Angular

Angular

Módulos

Routes En Angular

Angular

Enrutado Y Navegación

Router En Angular

Angular

Enrutado Y Navegación

La Directiva Routerlink En Angular

Angular

Enrutado Y Navegación

Parámetros En Rutas Con Activatedroute

Angular

Enrutado Y Navegación

Routes Sin Módulos En Angular

Angular

Enrutado Y Navegación

Servicios En Angular

Angular

Servicios E Inyección De Dependencias

Httpclient En Servicios De Angular

Angular

Servicios E Inyección De Dependencias

Formularios Con Ngmodel En Angular

Angular

Formularios

Formularios Reactivos En Angular

Angular

Formularios

Subir Archivo En Formularios

Angular

Formularios

Layout Con Angular Material

Angular

Integración Con Angular Material

Tablas En Angular Material

Angular

Integración Con Angular Material

Formularios En Angular Material

Angular

Integración Con Angular Material

Instalación Y Uso De Ngboostrap

Angular

Integración Con Bootstrap Css

Signals En Angular

Angular

Signals Y Reactividad

Effects En Angular

Angular

Signals Y Reactividad

Route Guards Basados En Interfaces

Angular

Autenticación Y Autorización

Guards Funcionales

Angular

Autenticación Y Autorización

Interceptores Http Basados En Interfaz

Angular

Autenticación Y Autorización

Interceptores Http Funcionales

Angular

Autenticación Y Autorización

Seguridad Jwt En Angular

Angular

Autenticación Y Autorización

Decodificar Tokens Jwt En Angular

Angular

Autenticación Y Autorización

Certificados de superación de Angular

Supera todos los ejercicios de programación del curso de Angular y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje 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.