Validar formularios en Ionic 2 con AngularJS


Validar formularios correctamente tanto en el lado cliente como en el lado servidor es una tarea importante para dejar pulidas nuestras aplicaciones. En esta entrada aprenderemos a validar formularios en Ionic 2 con AngularJS y esto nos será muy útil para comprobar que los datos que insertan los usuarios en los campos del propio formulario son válidos.


Como tenemos costumbre de hacer por cada una de las entradas creadas, vamos a ver cómo debería quedar nuestra aplicación al finalizar el desarrollo.
Podemos ver 2 páginas, la primera es el propio formulario y la segunda es la que mostraremos si los datos insertados en los inputs del formulario superan las validaciones.

¿Por dónde empezamos?

Vamos a empezar creando un nuevo proyecto y para ello ejecutamos en nuestra ventana de comandos las siguientes líneas:


1 – Creamos un nuevo proyecto:

ionic start blog-form-validation blank --v2

2 – Accedemos al directorio del proyecto, en mi caso:

cd blog-form-validation

3 – Creamos una nueva página dentro del proyecto:

ionic generate page form

Ya tenemos nuestro proyecto con una página llamada «form» y esta será la que utilicemos para crear el formulario.
Podíamos haber hecho uso de nuestra página «home» que viene por defecto en la instalación del framework, pero tenemos que empezar a acostumbrarnos a colocar cada código en su sitio correspondiente.

Novedades a la hora de crear una nueva página desde consola.

Las novedades que encontramos al crear una nueva página con ionic generator son que además del archivo nombre-pagina.ts también crea nombre-pagina.module.ts por cada página generada. Este archivo se utiliza para poder realizar lo que se conoce como Lazy Loading o carga diferida, que es un patrón de diseño comúnmente usado en la programación informática que consiste en retrasar la carga o inicialización de un objeto hasta el mismo momento de su utilización.

También importa IonicPage de ionic-angular y  el decorador @IonicPage() pero como no los vamos a utilizar, indicaré posteriormente su eliminación.

Cambiando la página de inicio de nuestra aplicación – corrigiendo bug.

Vamos a decirle a Ionic 2 que la página de inicio es «form», pero antes quiero destacar que debemos hacer un cambio en el código para que todo funcione correctamente ya que existe un pequeño bug en el framework y es que al crear una nueva página desde consola como hemos hecho, en el archivo form.ts se crea la clase Form en lugar de la clase FormPage, vamos a verlo en código para que quede todo mucho más claro.

1 – En el script que tenemos en /src/pages/form/form.ts cambiamos export class Form { por export class FormPage { y eliminamos la línea @IonicPage(). También IonicPage del import.

Con esto el bug quedaría solucionado pero como también me gusta mejorar la legibilidad del código, es por eso que hemos eliminado @IonicPage(). Realmente no vamos a hacer uso de ello, al menos por ahora.

Cambiando la página de inicio de nuestra aplicación – Home por Form.

1 – En el script que tenemos en /src/app/app.module.ts cambiamos…

import { HomePage } from '../pages/home/home';

… por…

import { FormPage } from '../pages/form/form';

… y en cada aparición de HomePage lo sustituimos por FormPage.

2 – En el script que tenemos en /src/app/app.component.ts hacemos lo mismo, cambiamos…

import { HomePage } from '../pages/home/home';

… por…

import { FormPage } from '../pages/form/form';

y  en cada aparición de HomePage lo sustituimos por FormPage.

3 – Eliminamos el script /src/pages/form/form.module.ts que nos ha generado la nueva versión 3.0.1 de Ionic ya que no lo vamos a necesitar.

4 – Eliminamos el directorio /src/pages/home tampoco vamos a hacer uso de ello.

Librerías y variables necesarias para Validar formularios en Ionic 2.

Ahora vamos a form.ts e incluimos las librerías necesarias las cuales nos permitirán realizar las posteriores tareas de validación gracias a AngularJS.

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

Creación de variables justo debajo de la línea export class FormPage {:
sampleForm es el identificador de nuestro formulario y termsAgree es el campo de términos y condiciones que necesitaremos clickar para que nuestro botón submit esté enabled.
sampleForm: FormGroup;
termsAgree: boolean;

Añadiendo la página de respuesta a la validación del formulario.

Continuamos con la creación de una nueva página, me refiero a la segunda de las imágenes que hemos visto al inicio de la entrada. Esta será mostrada tras validar los campos del formulario dando por hecho que el resultado sea correcto.

Una vez más y desde consola tecleamos lo siguiente: ionic generate page user. Una vez creada la nueva página también necesitamos eliminar el script /src/pages/user/user.module.ts ya que si no lo hacemos nos saltará un error. Y dentro de /src/pages/user/user.ts debemos eliminar @IonicPage() y también eliminar IonicPage del import al igual que hicimos en form.ts. A su vez, cambiamos la línea export class User { por export class UserPage { para solventar el bug del framework comentado.

De momento no vamos a trabajar en esta vista, tan solo necesitábamos crearla para su posterior uso.

Creando la vista del formulario.

Para entender cómo funciona este tipo de validación, haremos un formulario simple con 5 campos, los campos son: username, email, password, confirmPasword y agree. Aquí cada uno puede añadir los campos que necesite.

Os facilito el código de la vista /src/pages/form/form.html para agilizar vuestra tarea, pero no lancemos la aplicación aún ya que necesitamos construir la parte del controlador:

<!-- INICIO área cabecera -->
<ion-header>
  <ion-navbar>
    <ion-title>Ionic2 Forms</ion-title>
  </ion-navbar>
</ion-header>
<!-- FIN área cabecera -->

<!-- INICIO área de contenido-->
<ion-content padding class="form-content">

  <!-- INICIO del formulario -->
  <form [formGroup]="sampleForm" class="form" (ngSubmit)="onSubmit(sampleForm.value)" >

    <!-- INICIO del campo username -->
    <ion-item>
      <ion-label floating color="primary" >Nombre de usuario</ion-label>
      <ion-input type="text" formControlName="username" class="form-controll"></ion-input>
    </ion-item>
    <div *ngIf="formErrors.username">
      <div *ngFor="let errors of formErrors.username" >
        <p class="alert">{{ errors }}<p>
      </div>
    </div>
    <!-- FIN  del campo username -->

    <!-- INICIO  del campo email -->
    <ion-item>
      <ion-label floating color="primary" >Email</ion-label>
      <ion-input type="email" formControlName="email"></ion-input>
    </ion-item>
    <div *ngIf="formErrors.email">
      <div *ngFor="let errors of formErrors.email" >
        <p class="alert">{{ errors }}<p>
      </div>
    </div>
    <!-- FIN  del campo email -->

    <!-- INICIO  del campo password -->
    <ion-item>
      <ion-label floating color="primary" >Password</ion-label>
      <ion-input type="password" formControlName="password" validateEqual="confirmPassword" reverse="true"></ion-input>
    </ion-item>
    <div *ngIf="formErrors.password">
      <div *ngFor="let errors of formErrors.password" >
        <p class="alert">{{ errors }}<p>
      </div>
    </div>
    <!-- FIN  del campo password -->

    <!-- INICIO del campo confirmPassword -->
    <ion-item>
      <ion-label floating color="primary" >Confirma Password</ion-label>
      <ion-input type="password" formControlName="confirmPassword" validateEqual="password" reverse="false"></ion-input>
    </ion-item>
    <div *ngIf="formErrors.confirmPassword">
      <div *ngFor="let errors of formErrors.confirmPassword" >
        <p class="alert">{{ errors }}<p>
      </div>
    </div>
    <!-- FIN del campo confirmPassword -->

    <!-- INICIO del campo termsAgree -->
    <ion-item class="term">
      <ion-label class="terms-checkbox-label">I accept terms and conditions</ion-label>
      <ion-checkbox color="primary" formControlName="agree"></ion-checkbox>
    </ion-item>
    <p class="alert" [hidden]="termsAgree">You must accept terms and conditions</p>
    <!-- FIN del campo termsAgree -->

    <!-- Botón que envía datos del formulario -->
    <button ion-button full type="submit" [disabled]="!sampleForm.valid">Submit</button>

  </form>
  <!-- FIN del formulario -->

</ion-content>
<!-- FIN del área de contenidos -->

He creado algunas líneas a nivel de comentario para diferenciar elementos dentro de la vista, aunque doy por hecho que es sencillo y no tendréis problemas para entender el formulario. De todos modos como siempre digo, al finalizar la entrada disponéis de los enlaces necesarios para poder descargar todos los scripts que son modificados.

Paso también el código de la hoja de estilos /src/pages/form/form.scss

page-form {

  .term
  {
    padding-top: 20px;
  }

  .terms-checkbox-label
  {
    overflow: visible;
    text-overflow: initial;
    white-space: initial;
    opacity: 0.5;
    font-size: 14px;
    font-weight: bold;
  }

  .alert
  {
    color: red;
  }
}

Explicación de la página de form.html.

Ya sabemos que lo que tenemos entre las etiquetas <ion-header> corresponde a la cabecera de la página y título, por lo tanto poco tenemos que explicar aquí.

Veamos ahora la línea donde creamos la apertura del formulario ya que es más interesante, me refiero a: <form [formGroup]=»sampleForm» class=»form (ngSubmit)=»onSubmit(sampleForm.value)» >

[formGroup] es una etiqueta de Angular y lo que haces es agregar valores a cada elemento hijo [formControl], paso un ejemplo de un trozo de código del controlador para que se vea con más facilidad. Este será explicado cuando definamos el controlador.

this.sampleForm = new FormGroup({
    username: new FormControl('', Validators.compose([
        Validators.maxLength(25),
        Validators.minLength(5),
        Validators.pattern('^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$'),
        Validators.required
    ])),
}),

Con la línea this.sampleForm hacemos referencia al formulario en si, como si de un id o name en HTML se tratase.
Dentro del objeto new FormGroup será donde declaremos por cada uno de los elementos del formulario las validaciones necesarias.
Por último cada una de las líneas correspondientes a Validators.xxx con poco conocimiento de programación que tengamos, sabremos que se refieren a longitud máxima del campo, longitud mínima del campo, valores que acepta y si es obligatorio o no.

La etiqueta (ngSubmit)=»onSubmit(sampleForm.value)» lo que hace es que al ser pulsado el botón submit del formulario, se le pasen los valores de los campos al método definido en el controlador onSubmit.

Creando ionViewWillLoad en form.ts – Lógica para validar formularios en Ionic 2.

Ha llegado el momento de crear nuevos métodos en el controlador, empezaremos por ionViewWillLoad. Este método lo vamos a crear justo a continuación de ionViewDidLoad, no pasemos por alto que es ejecutado de forma automática tras las carga de la aplicación. Y en el vamos a definir lo siguiente:

ionViewWillLoad() {
    this.termsAgree = true;

    this.sampleForm = new FormGroup({
      username: new FormControl('', Validators.compose([
        Validators.maxLength(25),
        Validators.minLength(5),
        Validators.pattern('^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$'),
        Validators.required
      ])),
      email: new FormControl('', Validators.compose([
        Validators.required,
        Validators.pattern('^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$')
      ])),
      password: new FormControl('', Validators.compose([
        Validators.minLength(5),
        Validators.required,
        Validators.pattern('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9]+$')
      ])),
      confirmPassword: new FormControl('', Validators.required),
      agree: new FormControl(false, Validators.required)
    });

    this.sampleForm.valueChanges
      .debounceTime(400)
      .subscribe(data => this.onValueChanged(data));
}

Como podéis ver, estamos indicando para cada uno de los campos que hemos creado en la vista unos tipos de validación. Y en las últimas 3 líneas lo que hacemos es volver a llamar al método onValueChanged() cada vez que se realicen cambios en el formulario respetando un tiempo de 400 milisegundos.

Una vez aquí podemos apreciar que nos salta un error en la declaración .debounceTime(400), esto se debe a que para poder hacer uso de este método antes debemos llamar a la librería rxjs. Vamos entonces a añadir a continuación de los imports que ya tenemos lo siguiente:

import 'rxjs/add/operator/debounceTime';

Ampliando form.ts – Mensajes para mostrar en la vista.

Recordemos que en la vista form.html vemos condicionales de AngularJS como este *ngIf=»formErrors.username» y es ahora cuando vamos a construir la parte correspondiente en su controlador. De este modo si existe algún error, el controlador lanzará un error y la vista lo mostrará.

Seguimos picando código en form.ts y a continuación del método ionViewWillLoad() creamos lo siguiente:

formErrors = {
    'username': [],
    'email': [],
    'password': [],
    'confirmPassword': []
};

validationMessages = {
    'username': {
      'required':      'Username is required.',
      'minlength':     'Username must be at least 5 characters long.',
      'maxlength':     'Username cannot be more than 25 characters long.',
      'pattern':       'Your username must contain only numbers and letters.',
      'validUsername': 'Your username has already been taken.'
    },
    'email': {
      'required':      'Email is required',
      'pattern':       'Enter a valid email.'
    },
    'password': {
      'required':      'Password is required',
      'minlength':     'Password must be at least 5 characters long.',
      'pattern':       'Your password must contain at least one uppercase, one lowercase, and one number.'
    },
    'confirmPassword':{
      'required':      'Confirm password is required',
      'minlength':     'Confirm password must be at least 5 characters long.',
      'pattern':       'Your password must contain at least one uppercase, one lowercase, and one number.',
      'validateEqual': 'Password mismatch'
    }
};

En formErros declaramos cada uno de los campos que han de ser validados y en validationMessages asignamos los mensajes a cada una de las validaciones.

Nuevo método onValueChanged en form.ts – Detectando cambios en la vista.

Entre el método ionViewWillLoad() y la declaración de formErrors vamos a crear un nuevo método llamado onValueChanged() que será el encargado de volver a poner cada cosa en su sitio tras realizar cambios en la vista.

Este comprobará si existen cambios en los campos del formulario y en caso de existir los validará. Dando el caso de que no se superar las validaciones, entonces se mostrará el/los mensajes correspondientes por cada uno de los campos.

A continuación paso el trozo de código:

onValueChanged(data?: any) {
    if (!this.sampleForm) { return; }
    const form = this.sampleForm;
    for (const field in this.formErrors) {
      // Limpiamos los mensajes anteriores
      this.formErrors[field] = [];
      this.sampleForm[field] = '';
      const control = form.get(field);
      if (control && control.dirty && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          this.formErrors[field].push(messages[key]);
        }
      }
    }
}

Diseñando la vista user.html

Aquí lo que vamos a hacer es crear un texto en la vista del archivo /src/pages/user/user.html, tecleemos entre las etiquetas ion-content algo como «Los datos han sido procesados correctamente».

Y para que esta página pueda ser llamada desde la vista form.html en el constructor form.ts vamos a importar la página user:

import { UserPage } from '../user/user';

También necesitamos volver a /src/app/app.component.ts y vamos a añadir…

import { UserPage } from '../pages/user/user';

… haremos lo mismo en /src/app/app.module.ts pero en este segundo script debemos también añadir UserPage debajo de cada declaración de FormPage.

¿Qué nos queda por hacer?

Añadir una función onSubmit que permita navegar desde la vista del formulario form.html a la vista user.html donde se indicará que los datos han sido procesados. También cambiaremos el estado del botón de opción de términos y condiciones.

Aquí os paso el trozo de código del nuevo método para insertar dentro de form.ts.

onSubmit(values){
    if(values.agree){
      this.termsAgree = true;
      this.navCtrl.push(UserPage);
    }
    else{
      this.termsAgree = false;
    }
}

A continuación crearemos una validación para comprobar que los campos password son iguales… manos a la obra.

Creando validación comparativa entre los campos password.

Vamos a crear una carpeta llamada validators dentro del directorio /src y a su vez dentro de esta crearemos el script index.ts y password.validator.ts.

index.ts es el controlador de arranque, el que va a ser llamado siempre y dentro de este llamemos a tantas validaciones externas como sean necesarias, en nuestro caso solo a una, validar que los campos password sean iguales. Por lo tanto vamos a añadir dentro de index.ts la línea:

export { EqualValidator } from './password.validator';

Ahora llega el turno de password.validator.ts y el código que debemos insertar en este script es el que paso a continuación, no entraremos en detalle ya que son validaciones basadas en patrones y no quiero extender más el post.

import { Directive, forwardRef, Input } from '@angular/core';
import { Validator, AbstractControl, NG_VALIDATORS } from '@angular/forms';

export const EQUAL_VALIDATOR: any = {
  provide: NG_VALIDATORS,
  useExisting: forwardRef(() => EqualValidator),
  multi: true
};

@Directive({
  selector: '[validateEqual][formControlName],[validateEqual][formControl],[validateEqual][ngModel]',
  providers: [EQUAL_VALIDATOR]
})

export class EqualValidator implements Validator {

  @Input() validateEqual: string;
  @Input() reverse: string;

  private get isReverse() {
    if (!this.reverse) return false;
    return this.reverse === 'true' ? true: false;
  }

  validate(c: AbstractControl): { [key: string]: any } {
    // self value
    let v = c.value;

    // control value
    let e = c.root.get(this.validateEqual);

    // value not equal
    if (e && v !== e.value && !this.isReverse) {
      return {
        validateEqual: false
      }
    }

    // value equal and reverse
    if (e && v === e.value && this.isReverse) {
      delete e.errors['validateEqual'];
      if (!Object.keys(e.errors).length) e.setErrors(null);
    }

    // value not equal and reverse
    if (e && v !== e.value && this.isReverse) {
      e.setErrors({
          validateEqual: false
      })
    }

    return null;
  }
}

Para que estas validaciones funcionen correctaemente en nuestro formulario debemos añadir un par de cosas más en nuestros controladores.
Primero en /src/pages/form/form.ts añadimos:

import { EqualValidator } from '../../validators';

Y para terminar vamos a /src/app/app.module.ts y añadimos un último import…

import { EqualValidator } from '../validators';

… y dentro de declarations también añadimos EqualValidator.

Probando validar formularios en Ionic 2.

Ya sabéis lo que toca ahora… volvemos a la consola, lanzamos nuestra aplicación y podemos ver que las validaciones funcionan correctamente.
Y hasta aquí llega nuestra entrada de hoy, espero que os haya parecido entretenida y os sea de utilidad.

Descargas: https://drive.google.com/open?id=0B_sqK8lXq9J9eXNNWk5mSUhBMGs

Ah y recuerda!!!, si quieres estar al día acerca de la creación de nuevas entradas, no te olvides de suscribirte al blog.

[wysija_form id=»1″]

 

Versiones utilizadas, tiempo y dificultad de desarrollo:

Dificultad del ejercicio: Iniciación.
Tiempo de realización: 25 minutos.
Ionic versión: 2.2.1
Node versión: 7.5.0
NPM versión: 4.3.0
Cordova versión: 6.5.0
Sistema operativo: Windows

Publicaciones relacionadas

2 comentarios en “Validar formularios en Ionic 2 con AngularJS”

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Responsable de los datos: Luis María Jordán Muñoz | Finalidad: Responder a la solicitud que me envíes y ofrecerte información | Legitimación: Tu consentimiento de forma expresa | Destinatario: Webempresa mi proveedor de hosting | Derechos: Tienes derecho al acceso, rectificación, supresión, limitación, portabilidad y olvido, para más información, te dejo enlace a mi política de privacidad ... enlace

Ir arriba