Archivo de la etiqueta: formularios reactivos

Tutorial de IONIC: Formularios reactivos

Hola a todos, hoy vamos a hablar de los formularios reactivos.

En el siguiente enlace tienes el 铆ndice para acceder al resto de entradas de este tutorial:

En la aplicaci贸n de acertar n煤meros utilizamos [(ngModel)] para hacer Data Binding entre la vista y el controlador.

Aunque este enfoque es sencillo y es una caracter铆stica que viene heredando angular desde sus primeras versiones, en formularios complejos puede dificultar la gesti贸n de los campos, adem谩s est谩 previsto que se marque como deprecated en futuras versiones de Angular.聽

Por otro lado Angular nos ofrece los formularios reactivos que nos permite gestionar de una manera m谩s organizada聽 y escalable los campos de un formulario facilitando la validaci贸n.

Como siempre la mejor forma de comprender como funciona es con un ejemplo, as铆 que vamos a crear un nuevo proyecto al que llamaremos formulario:

ionic start formulario blank

Seleccionamos Angular como framework y para este ejemplo no es necesario integrar nuestra app con Capacitor.

El primer paso que debemos dar es importar ReactiveFormsModule en el m贸dulo de la p谩gina donde vayamos a utilizar un formulario, en este caso en home.module.ts.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HomePage } from './home.page';
 
import { HomePageRoutingModule } from './home-routing.module';
 
 
@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    ReactiveFormsModule,
    IonicModule,
    HomePageRoutingModule
  ],
  declarations: [HomePage]
})
export class HomePageModule {}

Crear un FormControl

Ahora en home.page.ts vamos a importar la clase FormControl y a crear una instancia para un campo que en este caso llamaremos nombre:

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
 
@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage {
 
  nombre = new FormControl('');
 
  constructor() {
  }
 
}

Vemos que al constructor FormControl le pasamos como par谩metro una cadena vac铆a (鈥樷), este es el valor inicial que tomar谩 el campo, si queremos que el campo tenga un valor de inicio podemos pasarle este valor en el constructor.

Veamos ahora c贸mo se utiliza un Form control en la plantilla, editamos el archivo home.page.html, eliminamos todo lo que hay dentro de ion-content y a帽adimos lo siguiente:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>
 
<ion-content>
  <ion-item>
    <ion-label>Nombre</ion-label>
    <ion-input [formControl]="nombre"></ion-input>
  </ion-item>
</ion-content>

Simplemente en el campo ion-input indicamos que este campo va a utilizar el formControl nombre.聽

Si queremos mostrar en tiempo real el valor de nuestro campo podemos utilizar el atributo value, para verlo vamos a a帽adir una etiqueta p donde se mostrar谩 el valor de nuestro campo a medida que vamos escribiendo:

<ion-header>
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>
 
<ion-content>
  <ion-item>
    <ion-label>Nombre:</ion-label>
    <ion-input [formControl]="nombre"></ion-input>
  </ion-item>
  <p class="ion-padding">{{ nombre.value }}</p>
</ion-content>

Si ejecutamos ahora nuestro proyecto al escribir algo en el campo nombre veremos como va apareciendo debajo los caracteres a medida que vamos escribiendo:

Los formularios reactivos tienen m茅todos que nos permiten cambiar el valor de un control mediante programaci贸n, lo que permite actualizar el valor sin la interacci贸n del usuario.

Por ejemplo vamos a crear un m茅todo en home.page.ts llamado cambiarNombre() que nos permita cambiar el valor del campo nombre:

cambiarNombre(){
  this.nombre.setValue('Ornitorrinco');
}

Con el m茅todo setValue asignamos el valor que deseemos al campo, en este caso el campo tomar谩 el valor 鈥極rnitorrinco鈥.

Para llamar a este m茅todo vamos a crear un bot贸n en home.page.html:

<ion-content>
  <ion-item>
    <ion-label>Nombre:</ion-label>
    <ion-input [formControl]="nombre"></ion-input>
  </ion-item>
  <p class="ion-padding">{{ nombre.value }}</p>
  <p class="ion-text-center">
    <ion-button (click)="cambiarNombre()">Cambiar nombre</ion-button>
  </p>
</ion-content>

Hemos metido el componente ion-button dentro de una etiqueta p con la clase ion-text-center para que salga centrado.

Si ejecutamos ahora nuestra aplicaci贸n veremos que al pulsar el bot贸n Cambiar nombre el campo nombre cambiar谩 su valor por la palabra Ornitorrinco.

Agrupando FormControls:聽

Los formularios generalmente contienen varios controles relacionados. Los formularios reactivos nos permiten agrupar m煤ltiples controles relacionados en un solo formulario de entrada.

FormGroup

Vamos a ver c贸mo podemos agrupar varios controles utilizando FormGroup.

Lo primero que debemos hacer es editar home.page.ts e importar FormGroup:

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

Ahora en lugar de el campo nombre que ten铆amos vamos a crear una propiedad llamada usuario que ser谩 un FormGroup que contendr谩 a su vez dos instancias tipo FormControl, una para el nombre y otra para el email, quedando de la siguiente manera:

...
export class HomePage {
 
  usuario = new FormGroup({
    nombre: new FormControl(''),
    email: new FormControl(''),
  });
 
  constructor() {
  }
...

Los controles de formulario individuales ahora se recopilan dentro de un grupo.

Vamos a modificar la vista en home.page.html para ver c贸mo se utilizar un FormGroup en la plantilla:

<ion-content>
  <form [formGroup]="usuario">
    <ion-item>
      <ion-label>Nombre:</ion-label>
      <ion-input formControlName="nombre"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Email:</ion-label>
      <ion-input formControlName="email"></ion-input>
    </ion-item>
 </form>
 <p class="ion-padding">{{ usuario.controls.nombre.value }}</p>
 <p class="ion-text-center">
   <ion-button (click)="cambiarNombre()">Cambiar nombre</ion-button>
 </p>
</ion-content>

Bien, vayamos por partes:

En primer lugar hemos a帽adido una etiqueta form a la cual mediante el par谩metro [formGroup] le indicamos que los campos del formularios van a estar asociados al FormGroup usuario que hemos creado en el controlador.

En el campo nombre ya no indicamos mediante el par谩metro [formControl] que lo asociamos con el control nombre sino que al pertenecer a un FormGroup necesitamos utilizar el par谩metro formControlName.

Hemos a帽adido otro campo para recoger el email del usuario, la estructura es identica al campo nombre, solo que como formControlName le asignamos email.

Dentro de la etiqueta p donde mostramos el contenido del campo nombre ya no podemos acceder directamente a nombre.value, ahora para acceder al valor del campo que pertenece a un FormGroup debemos especificarlo accediendo al control dentro del formgroup usuario de la siguiente manera:

<p class="ion-padding">{{ usuario.controls.nombre.value }}</p>

Si lo intentamos ejecutar nos dar谩 un error porque en el m茅todo cambiarNombre() estamos accediendo al valor del nombre directamente, debemos modificar este m茅todo en home.page.ts para que quede de la siguiente manera:

cambiarNombre(){
  this.usuario.controls.nombre.setValue('Ornitorrinco');
}

Guardar los datos del formulario:

Lo habitual es tener un un bot贸n en el formulario que al pulsarlo se procesan los datos del formulario, ya sea para enviarlos al servidor para guardarlos en una base de datos o para realizar cualquier operaci贸n con ellos.

Vamos a a帽adir ngSubmit a la etiqueta form para detectar cuando es lanzado el formulario y procesar los campos, en este caso le diremos que ejecute el m茅todo guardarDatos() que definiremos posteriormente en el controlador, por lo tanto la etiqueta form deber谩 quedar as铆:

 <form [formGroup]="usuario" (ngSubmit)="guardarDatos()">

Ahora para poder lanzar el formulario necesitamos incluir en el formulario un bot贸n de tipo submit:

<ion-content>
  <form [formGroup]="usuario" (ngSubmit)="guardarDatos()">
    <ion-item>
      <ion-label>Nombre:</ion-label>
      <ion-input formControlName="nombre"></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Email:</ion-label>
      <ion-input formControlName="email"></ion-input>
    </ion-item>
    <p class="ion-text-center">
      <ion-button type="submit">Guardar</ion-button>
    </p>  
  </form>
    <p class="ion-padding">{{ usuario.controls.nombre.value }}</p>
    <p class="ion-text-center">
      <ion-button (click)="cambiarNombre()">Cambiar nombre</ion-button>
    </p>
</ion-content>

Bien, ahora solo nos quedar铆a recoger los datos de nuestro formulario en la funci贸n guardarDatos() y hacer lo que necesitemos con ellos, en este caso simplemente vamos a mostrar en consola el contenido de los campos, por lo que en home.page.ts crearemos la funci贸n guardarDatos que quedar谩 de la siguiente forma:

 guardarDatos(){
   console.log(this.usuario.value);
 }

Si ejecutamos nuestra aplicaci贸n en el navegador, introducimos el nombre y el email y posteriormente pulsamos en el bot贸n Guardar veremos en la consola del navegador se mostrar谩 un objeto como este:

{ nombre: “Eduardo”, email: “edu.revilla.vaquero@gmail.com }

Con los datos del formulario contenidos en this.usuario.value realizaremos las operaciones que necesitemos.

Validar campos

Podemos validar los campos que introduce el usuario de una manera sencilla.

Lo primero que necesitamos es importar Validators de @angular/forms:

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

Ahora vamos a hacer que el campo nombre sea obligatorio y que adem谩s tenga que tener como m铆nimo 4 caracteres, adem谩s vamos a hacer que se compruebe que el campo email contiene una direcci贸n de email v谩lida.

Para ello vamos a modificar los FormsControls para que queden de la siguiente manera:

usuario = new FormGroup({
  nombre: new FormControl('', [Validators.required, Validators.minLength(4)]),
  email: new FormControl('', Validators.email),
});

Como vemos al crear el FormControl nombre le pasamos como segundo par谩metro un array con dos Validators, con Validators.required indicamos que el campo es obligatorio, y con Validators.minLength(4) le estamos diciendo que el campo tiene que tener al menos 4 caracteres.

Por otro lado al crear el FormControl email le pasamos como segundo par谩metro Validators.email que har谩 una comprobaci贸n de si el campo cumple con la estructura de un email v谩lido. Como el campo email solo tiene un 煤nico validador no es necesario que est茅 contenido en un array.

Ahora vamos a hacer que el bot贸n de guardar s贸lo est茅 activo cuando se cumplan las condiciones que le hemos marcado en los validadores. Editamos home.page.html y modificamos el bot贸n de guardar para que quede de la siguiente manera:

<ion-button type="submit" [disabled]="!usuario.valid">Guardar</ion-button>

Con esto le estamos indicando que el bot贸n est茅 deshabilitado si los campos del formGroup usuario no son v谩lidos.

Si ejecutas ahora la aplicaci贸n ver谩s que debemos cumplir con los requisitos que le hemos indicado para que se active el bot贸n de guardar.

Por 煤ltimo vamos a modificar la plantilla para mostrar mensajes de error cuando no se cumplan las validaciones, editamos home.page.html y modificamos el formulario para incluir lo siguiente:

<form [formGroup]="usuario" (ngSubmit)="guardarDatos()">
    <ion-item>
      <ion-label>Nombre:</ion-label>
      <ion-input formControlName="nombre"></ion-input>
    </ion-item>
    <ion-label color="danger" *ngIf="usuario.controls.nombre.errors?.required && (usuario.touched || usuario.dirty)">* El nombre es obligatorio </ion-label>
    <ion-label color="danger" *ngIf="usuario.controls.nombre.errors?.minlength && (usuario.touched || usuario.dirty)">* El nombre tiene que tener al menos 4 caracteres.</ion-label>
    <ion-item>
      <ion-label>Email:</ion-label>
      <ion-input formControlName="email"></ion-input>
    </ion-item>
    <ion-label color="danger" *ngIf="usuario.controls.email.errors?.email && (usuario.touched || usuario.dirty)">* El email no es v谩lido.</ion-label>
    <p class="ion-text-center">
      <ion-button type="submit" [disabled]="!usuario.valid">Guardar</ion-button>
    </p>  
  </form>

En el campo nombre hemos a帽adido dos componentes ion-label. En el primero mostramos un mensaje advirtiendo que el campo nombre es obligatorio, se mostrar谩 cuando se cumpla la condici贸n que le endicamos enla directiva ngIf:

<ion-label color="danger" *ngIf="usuario.controls.nombre.errors?.required && (usuario.touched || usuario.dirty)">* El nombre es obligatorio </ion-label>

En esta directiva comprobamos primero si se ha producido el error required, es decir que el campo est茅 vac铆o.聽

Accedemos al control a trav茅s de usuario.controls.nombre, dentro de este, en errors se encuentran los tipos de errores de validaci贸n que se hayan producido, observa que hemos puesto una interrogaci贸n despu茅s de errors, esto es porque si no se ha producido ning煤n error errors valdr谩 null y dar谩 un error al intentar acceder a la propiedad required. Poniendo una interrogaci贸n solo accede a esta propiedad si existe errors.

Adem谩s hemos a帽adido otra condici贸n que se debe cumplir: (usuario.touched || usuario.dirty) esta comprobaci贸n es para聽 evitar que el validador muestre errores antes de que el usuario tenga la oportunidad de editar el formulario.

En el segundo label comprobamos que haya pasado el validador minlength, para mostrar el error de que el nombre tiene que tener al menos 4 caracteres:

<ion-label color="danger" *ngIf="usuario.controls.nombre.errors?.minlength && (usuario.touched || usuario.dirty)">* El nombre tiene que tener al menos 4 caracteres.</ion-label>

Finalmente en el campo email mostramos un error si el email no es v谩lido:

<ion-label color="danger" *ngIf="usuario.controls.email.errors?.email && (usuario.touched || usuario.dirty)">* El email no es v谩lido.</ion-label>

FormBuilder

Para facilitar la tarea de crear formularios Angular nos proporciona el servicio FormBuilder.

Para utilizar FormBuilder debemos importarlo de @angular/forms e inyectarlo en el constructor:

import { FormBuilder } from '@angular/forms';
 
鈥

 constructor(private fb: FormBuilder) {
  }

FormBuilder nos permite definir los controles del formulario en forma de array haciendo mucho m谩s c贸modo.

En home.page.ts tenemos la definici贸n del nuestro FormGroup usuario:

usuario = new FormGroup({
  nombre: new FormControl('', [Validators.required, Validators.minLength(4)]),
  email: new FormControl('', Validators.email),
});

Lo vamos a sustituir por lo siguiente:

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

Como vemos no necesitamos llamar a new FormControl en cada control, simplemente le asignamos un array con sus propiedades, en este caso el valor por defecto que es una cadena vac铆a y los validadores.

Puede que al tener solo dos campos en este peque帽o ejemplo no le veas mucho ahorro, pero en formularios complejos con muchos campos hace que sea mucho m谩s c贸modo de definir los campos y facilita la lectura del c贸digo.

Si quieres saber m谩s sobre el desarrollo de aplicaciones con IONIC puedes adquirir mi libro, es esta entrada puedes ver el 铆ndice de contenidos del libro:

Libro: Desarrollo de aplicaciones m贸viles multiplataforma y PWAs con Ionic y Firebase desde cero.

Muchas gracias