tutoriales.com

Gestión de Formularios Reactivos en Angular: Creación, Validación y Control Dinámico 🚀

Este tutorial te guiará a través del universo de los formularios reactivos en Angular, desde la configuración inicial hasta técnicas avanzadas de validación y manipulación dinámica. Aprenderás a construir formularios robustos y eficientes que mejoren la experiencia del usuario y la integridad de los datos.

Intermedio20 min de lectura8 views
Reportar error

Los formularios son el corazón de casi cualquier aplicación web, y Angular ofrece dos enfoques poderosos para manejarlos: los formularios basados en plantillas y los formularios reactivos. En este tutorial, nos centraremos exclusivamente en los formularios reactivos, que proporcionan un modelo más robusto, escalable y predecible, ideal para aplicaciones complejas donde el control y la validación son cruciales.

🎯 ¿Por qué Formularios Reactivos? Una Visión General

Los formularios reactivos utilizan un modelo impulsado por la programación reactiva, lo que significa que la lógica del formulario se define en el código de tu componente, no en la plantilla HTML. Esto ofrece una serie de ventajas:

  • Claridad y Predictibilidad: El modelo reactivo es síncrono, lo que facilita la prueba y depuración.
  • Escalabilidad: Ideal para formularios complejos con validación personalizada y lógica condicional.
  • Reutilización: Permite crear validadores y controles personalizados que se pueden reutilizar fácilmente.
  • Tipado Fuerte: Mejora la detección de errores en tiempo de compilación.
  • Inmutabilidad: Los cambios en los formularios reactivos son inmutables, lo que facilita el seguimiento de los estados.
📌 Nota: Si eres nuevo en Angular, te recomiendo familiarizarte con los fundamentos de RxJS, ya que los formularios reactivos hacen un uso intensivo de observables.

🛠️ Configuración Inicial: Preparando el Terreno

Antes de sumergirnos en la creación de formularios, necesitamos importar el módulo necesario en nuestra aplicación Angular.

📦 Importando ReactiveFormsModule

Para usar formularios reactivos, debes importar ReactiveFormsModule en el AppModule o en cualquier módulo donde vayas a utilizar estos formularios.

// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms'; // ¡Importa esto!

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule // ¡Añádelo a los imports!
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

🧱 Creación de Formularios Reactivos Básicos

Los formularios reactivos se construyen usando tres clases principales: FormControl, FormGroup y FormArray.

📝 FormControl: El Pilar Individual

FormControl es la unidad más básica de un formulario reactivo. Representa un campo de entrada individual, como un <input>, <select> o <textarea>.

Creando un FormControl:

// src/app/mi-componente/mi-componente.component.ts
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
  selector: 'app-mi-componente',
  templateUrl: './mi-componente.component.html',
  styleUrls: ['./mi-componente.component.css']
})
export class MiComponenteComponent {
  nombre = new FormControl(''); // Valor inicial vacío

  constructor() {
    // Observar cambios en el valor del control
    this.nombre.valueChanges.subscribe(value => {
      console.log('El nombre ha cambiado a:', value);
    });

    // Observar cambios en el estado (válido, inválido, etc.)
    this.nombre.statusChanges.subscribe(status => {
      console.log('El estado del nombre es:', status);
    });
  }

  actualizarNombre() {
    this.nombre.setValue('Juan Pérez'); // Establecer un nuevo valor programáticamente
  }
}

Enlazando con la plantilla:

<!-- src/app/mi-componente/mi-componente.component.html -->
<div>
  <label for="nombre">Nombre:</label>
  <input type="text" id="nombre" [formControl]="nombre">
  <p>Valor actual: {{ nombre.value }}</p>
  <p>Estado: {{ nombre.status }}</p>
  <button (click)="actualizarNombre()">Actualizar Nombre</button>
</div>

🧩 FormGroup: Agrupando Controles

Un FormGroup es una colección de FormControls (y otros FormGroups o FormArrays). Permite gestionar el estado de un formulario completo como una sola unidad.

Creando un FormGroup:

// src/app/formulario-usuario/formulario-usuario.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-formulario-usuario',
  templateUrl: './formulario-usuario.component.html',
  styleUrls: ['./formulario-usuario.component.css']
})
export class FormularioUsuarioComponent {
  formularioUsuario = new FormGroup({
    nombre: new FormControl('', Validators.required),
    email: new FormControl('', [Validators.required, Validators.email]),
    edad: new FormControl(null, [Validators.min(18), Validators.max(99)])
  });

  constructor() {
    this.formularioUsuario.valueChanges.subscribe(value => {
      console.log('Valor del formulario:', value);
    });

    this.formularioUsuario.statusChanges.subscribe(status => {
      console.log('Estado del formulario:', status);
    });
  }

  onSubmit() {
    if (this.formularioUsuario.valid) {
      console.log('Formulario enviado:', this.formularioUsuario.value);
      // Aquí puedes enviar los datos a un servicio o API
    } else {
      console.log('Formulario inválido. Por favor, revisa los campos.');
      // Opcional: Marcar todos los controles como 'touched' para mostrar errores
      this.formularioUsuario.markAllAsTouched();
    }
  }

  establecerValoresPredeterminados() {
    this.formularioUsuario.setValue({
      nombre: 'Ana García',
      email: 'ana.garcia@example.com',
      edad: 25
    });
  }

  parchearValores() {
    this.formularioUsuario.patchValue({
      nombre: 'Carlos Ruiz' // Solo actualiza el nombre
    });
  }
}

Enlazando con la plantilla:

<!-- src/app/formulario-usuario/formulario-usuario.component.html -->
<form [formGroup]="formularioUsuario" (ngSubmit)="onSubmit()">
  <div>
    <label for="nombre">Nombre:</label>
    <input type="text" id="nombre" formControlName="nombre">
    <div *ngIf="formularioUsuario.get('nombre')?.invalid && formularioUsuario.get('nombre')?.touched" class="error-message">
      El nombre es obligatorio.
    </div>
  </div>

  <div>
    <label for="email">Email:</label>
    <input type="email" id="email" formControlName="email">
    <div *ngIf="formularioUsuario.get('email')?.invalid && formularioUsuario.get('email')?.touched" class="error-message">
      <span *ngIf="formularioUsuario.get('email')?.errors?.['required']">El email es obligatorio.</span>
      <span *ngIf="formularioUsuario.get('email')?.errors?.['email']">Por favor, introduce un email válido.</span>
    </div>
  </div>

  <div>
    <label for="edad">Edad:</label>
    <input type="number" id="edad" formControlName="edad">
    <div *ngIf="formularioUsuario.get('edad')?.invalid && formularioUsuario.get('edad')?.touched" class="error-message">
      <span *ngIf="formularioUsuario.get('edad')?.errors?.['min']">La edad mínima es 18.</span>
      <span *ngIf="formularioUsuario.get('edad')?.errors?.['max']">La edad máxima es 99.</span>
    </div>
  </div>

  <button type="submit" [disabled]="formularioUsuario.invalid">Enviar</button>
  <button type="button" (click)="establecerValoresPredeterminados()">Establecer Valores</button>
  <button type="button" (click)="parchearValores()">Parchear Valores</button>
</form>

<p>Valor del formulario: {{ formularioUsuario.value | json }}</p>
<p>Estado del formulario: {{ formularioUsuario.status }}</p>

<style>
  .error-message { color: red; font-size: 0.8em; margin-top: 5px; }
</style>
💡 Consejo: Usa `setValue()` para reemplazar *todos* los valores del formulario y `patchValue()` para actualizar *algunos* valores.

✅ Validación de Formularios Reactivos

La validación es fundamental para asegurar la integridad de los datos. Angular proporciona validadores incorporados y permite crear validadores personalizados.

📝 Validadores Incorporados

Angular ofrece una serie de validadores predefinidos en la clase Validators:

ValidadorDescripción
------
Validators.requiredEl control no puede estar vacío.
Validators.emailEl valor debe ser una dirección de email válida.
------
Validators.minLength(n)El valor debe tener al menos n caracteres.
Validators.maxLength(n)El valor debe tener como máximo n caracteres.
------
Validators.min(n)El valor debe ser un número igual o mayor que n.
Validators.max(n)El valor debe ser un número igual o menor que n.
------
Validators.pattern(regex)El valor debe coincidir con la expresión regular.

Puedes aplicar uno o varios validadores a un FormControl pasándolos como segundo argumento a su constructor, ya sea individualmente o como un array.

new FormControl('', Validators.required); // Un validador
new FormControl('', [Validators.required, Validators.email, Validators.maxLength(50)]); // Múltiples validadores

✨ Validadores Personalizados Síncronos

Los validadores personalizados son funciones que reciben un AbstractControl (que puede ser un FormControl, FormGroup o FormArray) y devuelven null si el control es válido, o un objeto con un par clave-valor si es inválido (ej. { 'nombreInvalido': true }).

Ejemplo: Validador para un nombre de usuario que no contenga 'admin'

// src/app/validadores/username.validator.ts
import { AbstractControl, ValidatorFn, ValidationErrors } from '@angular/forms';

export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    const forbidden = nameRe.test(control.value);
    return forbidden ? { 'forbiddenName': { value: control.value } } : null;
  };
}

// Usándolo en un componente:
// import { forbiddenNameValidator } from '../validadores/username.validator';
// ...
// nombreUsuario: new FormControl('', [Validators.required, forbiddenNameValidator(/admin/i)])

⚡ Validadores Personalizados Asíncronos

Los validadores asíncronos son útiles para validaciones que requieren una llamada a un servidor (ej. verificar si un nombre de usuario ya existe). Devuelven una Promise o un Observable que eventualmente emite null o un objeto de errores.

Ejemplo: Validador asíncrono para comprobar la disponibilidad de un email

// src/app/validadores/async-email.validator.ts
import { AbstractControl, AsyncValidatorFn, ValidationErrors } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { map, catchError, debounceTime, take } from 'rxjs/operators';
import { UserService } from '../services/user.service'; // Asume un servicio de usuario

export function uniqueEmailValidator(userService: UserService): AsyncValidatorFn {
  return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
    if (!control.value) {
      return of(null); // No validar si el campo está vacío
    }
    return control.valueChanges.pipe(
      debounceTime(500), // Espera 500ms antes de enviar la petición
      take(1), // Toma solo el primer valor después del debounce
      map(value => value ? value.trim() : ''), // Trim spaces
      // Simulación de llamada al servicio
      // En un caso real, llamarías a userService.checkEmailAvailability(control.value)
      map((email: string) => {
        const isEmailTaken = ['test@example.com', 'admin@example.com'].includes(email);
        return isEmailTaken ? { 'emailTaken': true } : null;
      }),
      catchError(() => of(null)) // Manejo de errores de la petición
    );
  };
}

// Usándolo en un componente:
// import { uniqueEmailValidator } from '../validadores/async-email.validator';
// import { UserService } from '../services/user.service';
// ...
// constructor(private userService: UserService) { ... }
// email: new FormControl('', [Validators.required, Validators.email], [uniqueEmailValidator(this.userService)])
⚠️ Advertencia: Los validadores asíncronos deben colocarse como el *tercer* argumento del constructor de `FormControl` (o `FormGroup`/`FormArray`).

🔄 Manipulación Dinámica de Formularios

Una de las mayores ventajas de los formularios reactivos es su capacidad para ser modificados dinámicamente en tiempo de ejecución.

➕➖ Añadir y Quitar Controles (con FormGroup)

Puedes añadir o quitar FormControls o FormGroups a un FormGroup existente usando los métodos addControl() y removeControl().

// src/app/dynamic-form/dynamic-form.component.ts
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html',
  styleUrls: ['./dynamic-form.component.css']
})
export class DynamicFormComponent {
  dynamicForm = new FormGroup({
    firstName: new FormControl('', Validators.required),
    lastName: new FormControl('', Validators.required)
  });

  constructor() { }

  addPhoneNumber() {
    this.dynamicForm.addControl('phoneNumber', new FormControl('', Validators.pattern(/^\d{9}$/)));
  }

  removePhoneNumber() {
    if (this.dynamicForm.contains('phoneNumber')) {
      this.dynamicForm.removeControl('phoneNumber');
    }
  }

  onSubmit() {
    console.log(this.dynamicForm.value);
  }
}
<!-- src/app/dynamic-form/dynamic-form.component.html -->
<form [formGroup]="dynamicForm" (ngSubmit)="onSubmit()">
  <label>First Name:</label>
  <input formControlName="firstName">

  <label>Last Name:</label>
  <input formControlName="lastName">

  <ng-container *ngIf="dynamicForm.contains('phoneNumber')">
    <label>Phone Number:</label>
    <input formControlName="phoneNumber">
    <div *ngIf="dynamicForm.get('phoneNumber')?.invalid && dynamicForm.get('phoneNumber')?.touched" class="error-message">
      Número de teléfono inválido.
    </div>
  </ng-container>

  <button type="button" (click)="addPhoneNumber()">Add Phone Number</button>
  <button type="button" (click)="removePhoneNumber()">Remove Phone Number</button>
  <button type="submit">Submit</button>
</form>

📜 FormArray: Gestionando Listas de Controles

FormArray es ideal para manejar colecciones dinámicas de controles, como una lista de direcciones de email o una lista de habilidades.

Ejemplo: Lista de Habilidades

// src/app/skill-form/skill-form.component.ts
import { Component } from '@angular/core';
import { FormArray, FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'app-skill-form',
  templateUrl: './skill-form.component.html',
  styleUrls: ['./skill-form.component.css']
})
export class SkillFormComponent {
  profileForm = new FormGroup({
    name: new FormControl('', Validators.required),
    skills: new FormArray([ // Un FormArray de FormGroups
      this.createSkillFormGroup() // Habilidad inicial
    ])
  });

  get skills(): FormArray {
    return this.profileForm.get('skills') as FormArray;
  }

  createSkillFormGroup(): FormGroup {
    return new FormGroup({
      skillName: new FormControl('', Validators.required),
      level: new FormControl('Junior', Validators.required)
    });
  }

  addSkill() {
    this.skills.push(this.createSkillFormGroup());
  }

  removeSkill(index: number) {
    this.skills.removeAt(index);
  }

  onSubmit() {
    console.log('Formulario de perfil con habilidades:', this.profileForm.value);
  }
}
<!-- src/app/skill-form/skill-form.component.html -->
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
  <div>
    <label for="name">Nombre:</label>
    <input type="text" id="name" formControlName="name">
    <div *ngIf="profileForm.get('name')?.invalid && profileForm.get('name')?.touched" class="error-message">
      El nombre es obligatorio.
    </div>
  </div>

  <h3>Habilidades:</h3>
  <div formArrayName="skills">
    <div *ngFor="let skill of skills.controls; let i = index" [formGroupName]="i" class="skill-group">
      <label for="skillName-{{i}}">Habilidad {{ i + 1 }}:</label>
      <input type="text" id="skillName-{{i}}" formControlName="skillName">
      <select formControlName="level">
        <option value="Junior">Junior</option>
        <option value="Mid">Mid</option>
        <option value="Senior">Senior</option>
      </select>
      <button type="button" (click)="removeSkill(i)">Eliminar</button>
      <div *ngIf="skill.get('skillName')?.invalid && skill.get('skillName')?.touched" class="error-message">
        El nombre de la habilidad es obligatorio.
      </div>
    </div>
  </div>
  <button type="button" (click)="addSkill()">Añadir Habilidad</button>

  <button type="submit" [disabled]="profileForm.invalid">Guardar Perfil</button>
</form>

<p>Valor del perfil: {{ profileForm.value | json }}</p>

<style>
  .skill-group { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; border-radius: 5px; }
</style>
FormGroup: profileForm (Raíz del formulario) FormControl 'name' FormArray: 'skills' (Colección dinámica) FormGroup [0] 'skillName' 'level' FormGroup [1] 'skillName' 'level' Group Control Array

🔄 Estado y Actualizaciones de los Controles

Cada control de formulario (FormControl, FormGroup, FormArray) tiene propiedades y métodos que te permiten verificar su estado y manipularlo.

📊 Propiedades de Estado Comunes

  • value: El valor actual del control.
  • status: El estado de validación ('VALID', 'INVALID', 'PENDING', 'DISABLED').
  • valid: true si el control es válido, false en caso contrario.
  • invalid: true si el control es inválido, false en caso contrario.
  • pending: true si el control está ejecutando una validación asíncrona.
  • disabled: true si el control está deshabilitado.
  • enabled: true si el control está habilitado.
  • touched: true si el usuario ha interactuado con el control (ha perdido el foco).
  • untouched: true si el usuario no ha interactuado con el control.
  • dirty: true si el valor del control ha cambiado desde su valor inicial.
  • pristine: true si el valor del control no ha cambiado desde su valor inicial.
  • errors: Un objeto que contiene los errores de validación, si el control es inválido.
90% de Cobertura de Propiedades de Estado

⚙️ Métodos de Manipulación

  • setValue(value: any, options?: Object): Establece un nuevo valor para un solo control.
  • patchValue(value: Object, options?: Object): Actualiza algunos valores de un control o grupo de controles.
  • reset(value?: any, options?: Object): Restablece el control a su valor inicial o a un valor específico.
  • disable(options?: Object): Deshabilita el control.
  • enable(options?: Object): Habilita el control.
  • markAsDirty(options?: Object): Marca el control como 'dirty'.
  • markAsPristine(options?: Object): Marca el control como 'pristine'.
  • markAsTouched(options?: Object): Marca el control como 'touched'.
  • markAsUntouched(options?: Object): Marca el control como 'untouched'.
Ejemplo de uso de `disable` y `enable`
// En tu componente .ts
habilitarEmail() {
  this.formularioUsuario.get('email')?.enable();
}

deshabilitarEmail() {
  this.formularioUsuario.get('email')?.disable();
}
<!-- En tu plantilla .html -->
<button type="button" (click)="habilitarEmail()">Habilitar Email</button>
<button type="button" (click)="deshabilitarEmail()">Deshabilitar Email</button>

🌐 FormBuilder: Simplificando la Creación de Formularios

Para formularios más complejos, escribir new FormGroup({...}) y new FormControl(...) repetidamente puede ser tedioso. Angular proporciona el servicio FormBuilder para simplificar esta tarea.

🏭 Usando FormBuilder

Inyecta FormBuilder en tu constructor y usa sus métodos para crear controles y grupos.

// src/app/form-builder-example/form-builder-example.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators, FormArray } from '@angular/forms';

@Component({
  selector: 'app-form-builder-example',
  templateUrl: './form-builder-example.component.html',
  styleUrls: ['./form-builder-example.component.css']
})
export class FormBuilderExampleComponent implements OnInit {
  userProfileForm!: FormGroup; // Usamos '!' para indicar que será inicializado en ngOnInit

  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
    this.userProfileForm = this.fb.group({
      firstName: ['', Validators.required],
      lastName: ['', Validators.required],
      address: this.fb.group({
        street: ['', Validators.required],
        city: [''],
        zip: ['', Validators.pattern(/^\d{5}$/)]
      }),
      phones: this.fb.array([
        this.fb.control('', Validators.pattern(/^\d{9}$/))
      ])
    });
  }

  get phones(): FormArray {
    return this.userProfileForm.get('phones') as FormArray;
  }

  addPhone() {
    this.phones.push(this.fb.control('', Validators.pattern(/^\d{9}$/)));
  }

  removePhone(index: number) {
    this.phones.removeAt(index);
  }

  onSubmit() {
    if (this.userProfileForm.valid) {
      console.log('Formulario enviado (FormBuilder):', this.userProfileForm.value);
    } else {
      console.log('Formulario inválido (FormBuilder).');
    }
  }
}
<!-- src/app/form-builder-example/form-builder-example.component.html -->
<form [formGroup]="userProfileForm" (ngSubmit)="onSubmit()">
  <label>First Name:</label>
  <input formControlName="firstName">

  <label>Last Name:</label>
  <input formControlName="lastName">

  <div formGroupName="address">
    <h3>Address</h3>
    <label>Street:</label>
    <input formControlName="street">

    <label>City:</label>
    <input formControlName="city">

    <label>Zip Code:</label>
    <input formControlName="zip">
  </div>

  <h3>Phones</h3>
  <div formArrayName="phones">
    <div *ngFor="let phoneControl of phones.controls; let i = index">
      <input [formControlName]="i">
      <button type="button" (click)="removePhone(i)">Remove</button>
    </div>
  </div>
  <button type="button" (click)="addPhone()">Add Phone</button>

  <button type="submit" [disabled]="userProfileForm.invalid">Submit</button>
</form>
<p>Valor del formulario (FormBuilder): {{ userProfileForm.value | json }}</p>
🔥 Importante: `FormBuilder` es simplemente un servicio de conveniencia. Internamente, sigue creando instancias de `FormControl`, `FormGroup` y `FormArray`.

🧪 Pruebas de Formularios Reactivos

Uno de los grandes beneficios de los formularios reactivos es su facilidad para ser probados de forma unitaria, ya que toda la lógica reside en el código TypeScript.

🔬 Ejemplo de Prueba Unitaria

Consideremos el FormularioUsuarioComponent que creamos antes.

// src/app/formulario-usuario/formulario-usuario.component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule, FormGroup, FormControl, Validators } from '@angular/forms';
import { FormularioUsuarioComponent } from './formulario-usuario.component';

describe('FormularioUsuarioComponent', () => {
  let component: FormularioUsuarioComponent;
  let fixture: ComponentFixture<FormularioUsuarioComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ FormularioUsuarioComponent ],
      imports: [ ReactiveFormsModule ]
    })
    .compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(FormularioUsuarioComponent);
    component = fixture.componentInstance;
    fixture.detectChanges(); // Ejecuta ngOnInit y crea el formulario
  });

  it('should create the form', () => {
    expect(component.formularioUsuario).toBeInstanceOf(FormGroup);
    expect(component.formularioUsuario.get('nombre')).toBeInstanceOf(FormControl);
    expect(component.formularioUsuario.get('email')).toBeInstanceOf(FormControl);
  });

  it('should make nombre control required', () => {
    const nombreControl = component.formularioUsuario.get('nombre');
    nombreControl?.setValue('');
    expect(nombreControl?.valid).toBeFalsy();
    expect(nombreControl?.errors?.['required']).toBeTruthy();
  });

  it('should make email control required and valid email format', () => {
    const emailControl = component.formularioUsuario.get('email');

    emailControl?.setValue('');
    expect(emailControl?.valid).toBeFalsy();
    expect(emailControl?.errors?.['required']).toBeTruthy();

    emailControl?.setValue('invalid-email');
    expect(emailControl?.valid).toBeFalsy();
    expect(emailControl?.errors?.['email']).toBeTruthy();

    emailControl?.setValue('test@example.com');
    expect(emailControl?.valid).toBeTruthy();
    expect(emailControl?.errors).toBeNull();
  });

  it('should make edad control between 18 and 99', () => {
    const edadControl = component.formularioUsuario.get('edad');

    edadControl?.setValue(17);
    expect(edadControl?.valid).toBeFalsy();
    expect(edadControl?.errors?.['min']).toBeTruthy();

    edadControl?.setValue(100);
    expect(edadControl?.valid).toBeFalsy();
    expect(edadControl?.errors?.['max']).toBeTruthy();

    edadControl?.setValue(25);
    expect(edadControl?.valid).toBeTruthy();
    expect(edadControl?.errors).toBeNull();
  });

  it('should disable submit button when form is invalid', () => {
    // Formulario inicialmente inválido porque 'nombre' y 'email' son requeridos
    fixture.detectChanges(); // Actualiza la vista para que el botón refleje el estado
    const submitButton = fixture.nativeElement.querySelector('button[type="submit"]');
    expect(submitButton.disabled).toBeTrue();

    component.formularioUsuario.setValue({
      nombre: 'Test User',
      email: 'test@example.com',
      edad: 30
    });
    fixture.detectChanges(); // Actualiza la vista después de cambiar el valor
    expect(submitButton.disabled).toBeFalse();
  });

  it('should call onSubmit when form is valid', () => {
    spyOn(component, 'onSubmit');

    component.formularioUsuario.setValue({
      nombre: 'Test User',
      email: 'test@example.com',
      edad: 30
    });

    const form = fixture.nativeElement.querySelector('form');
    form.dispatchEvent(new Event('submit'));
    expect(component.onSubmit).toHaveBeenCalled();
  });
});
💡 Consejo: Al probar validaciones, asegúrate de establecer valores y luego verificar las propiedades `valid`, `invalid`, `errors` del `FormControl`.

🚀 Conclusión: Dominando los Formularios Reactivos

Los formularios reactivos de Angular son una herramienta increíblemente potente y flexible para manejar la entrada de datos en tus aplicaciones. Proporcionan un control granular sobre el estado del formulario, facilitan la validación compleja y personalizada, y se integran perfectamente con el paradigma de la programación reactiva. Al dominar FormControl, FormGroup, FormArray y FormBuilder, estarás bien equipado para construir interfaces de usuario robustas y dinámicas que responden de manera inteligente a la interacción del usuario.

Recuerda que la práctica es clave. Intenta crear diferentes tipos de formularios, experimenta con validadores síncronos y asíncronos, y no dudes en explorar la documentación oficial de Angular para profundizar aún más. ¡Feliz codificación! ✨

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!