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:
Indice Tutorial Ionic >>
- Libro: Desarrollo de aplicaciones móviles multiplataforma y PWAs con Ionic y Firebase desde cero.
- Tutorial de IONIC: Introducción
- Tutorial de IONIC: Estructura de un proyecto en IONIC
- Tutorial de IONIC:Mini Juego de acertar números (actualizado)
- Tutorial de IONIC: Navegación
- Tutorial de IONIC: Menú lateral
- Tutorial de IONIC: Navegación por Tabs
- Tutorial de IONIC: Componentes personalizados
- Tutorial de IONIC: Peticiones http
- Tutorial de IONIC: Formularios reactivos
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 ‘Ornitorrinco’.
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