Formularios en AngularJS: componentes y validación.

2
37493

El objetivo de este tutorial es mostrar buenas prácticas en la gestión de formularios, componentes y validación de los mismos en AngularJS.

0. Índice de contenidos.


1. Introducción

Qué framework MVC, o MVVM como prefiráis, escrito en el lenguaje que sea, no proporcionaría un mecanismo de
fábrica para permitir al usuario introducir información a través de la interfaz visual mediante componentes de
formulario y al programador que la diseña declarar constraints o validaciones para que esa información no llege
al modelo, ni se invoque al evento del controlador correspondiente, si no pasan tales validaciones, ¿qué tipo de framework sería? 😉

En este tutorial vamos diseñar nuestro propio fomulario en AngularJS siguiendo las recomendaciones del
framework para entrar dentro del ciclo de vida de la validación y los estados de los componentes de un formulario.

Obtendremos una mejor experiencia de usuario realizando las validaciones de formularios en cliente, pero no olvidemos
que el cliente no deja de ser un «burdo» javascript que cualquiera con conocimientos puede manipular o deshabilitar,
no deja de interpretarse por un navegador; con lo que los servicios de backEnd deben ser robustos y realizar una doble
validación, si o bajo vuestra responsabilidad.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Mavericks 10.9.4
  • AngularJS 1.4.3.
  • Plunker


3. Componentes.

Ya comentamos que AngularJS proporciona directivas que convierten los componentes
nativos de HTML5 en controles propios de formulario.

La directiva más importante aunque no lo parezca es la que demarca el contenido del formulario, un formulario no
es un formulario sin un componente <form … > … </form>; otra cosa es que AngularJS por el binding y
la «magia» del mismo traslade al modelo la información de un <input type=»text» con un ng-model aunque no esté demarcado
por un <form pero sin esta etiqueta no podremos gestionar correctamente un formulario en AngularJS.



  <div ng-controller="UserController">
    <form name="userForm" novalidate>
      <label for="name">Nombre</label><br />
      <input name="name" type="text" ng-model="user.name" />
      <br />
      <label for="email">Email</label><br />
      <input name="email" type="email" ng-model="user.email" />
      <br />
      <label for="gender">Género</label><br />
      <input name="gender" type="radio" ng-model="user.gender" value="male" />Masculino
      <input name="gender" type="radio" ng-model="user.gender" value="female" />Femenino
      <br />
      <label for="age">Edad</label><br />
      <input name="age" type="number" ng-model="user.age" />
      
      <br /><br />
      <input type="button" ng-click="reset()" value="Limpiar" />
      <input type="submit" ng-click="update()" value="Guardar" />
    </form>
    <pre>user = {{user | json}}</pre>
  </div>

  <script>
    angular.module('formLabs', [])
      .controller('UserController', ['$scope', function($scope) {
        $scope.user = {};

        $scope.update = function() {
          console.log($scope.user);
        };

        $scope.reset = function() {
          $scope.user = {};
        };

        $scope.reset();
      }]);
  </script>
</body>

Lo primero que llama la atención es que un formulario no tiene acción,
la vinculación entre la vista y el modelo la gestiona el framework y el envío de la información al servidor
será responsabilidad de un servicio en el que delege el controlador; ahora solo estamos sacando una traza por consola.

De lo visto es interesante también:

  • el atributo novalidate de HTML5 que indica que el contenido del formulario no tiene que ser validado por el navegador.
    Si incluimos componentes propios de HTML5 el propio navegador valida el contenido y quedaría inconsistente que existan
    componentes que los valide el navegador y otros el propio ciclo de vida de AngularJS.
  • que el mero uso de la directiva ng-model convierta un componente de HTML5 como en el caso del input
  • el filtro {{user | json}} que convierte a json un objeto para imprimirlo en el propio HTML y ver el contenido
  • si el ccontenido de un componente insertado por el usuario no pasa la validación, como puede ser en el formulario actual el correo electrónico incorrecto,
    el valor no se traslada al modelo hasta que sea correcto,
  • por defecto el ng-model vincula al modelo la información de la vista en cuanto se produce un cambio, pero ese
    comportamiento se puede modificar usando la directiva ngModelOptions que permite especificar un listado de eventos:
    ng-model-options=»{ updateOn: ‘blur’ }» solo traslada la información al modelo cuando pierde el foco.
    Se pueden especificar muchos eventos delimitados por espacios ng-model-options=»{ updateOn: ‘mousedown blur’ }»
  • proporcionar un nombre a los componentes de formulario siempre es una buena práctica, si el formulario se enviase
    por POST a la acción definida en el mismo, el nombre identifica el parámetro y, aquí con AngularSJS, el nombre
    tanto a nivel de fomulario como a nivel de componente expone el estado interno del controlador que los gestiona
    para poder acceder a las propiedades del mismo y conocer, por ejemplo, si un componente «ha sido tocado» o
    el formulario ha sido submitido.


4. Validación.

Como hemos comentado AngularJS proporciona directivas para todos los tipos de componentes de formulario de HTML5
(text, number, url, email, date, radio, checkbox) y con esa tipología proporciona una validación (required, pattern, minlength, maxlength, min, max) y
también podemos crearnos nuestros propios validadores.

A continuación vamos a modificar el ejemplo inicial para añadir validaciones de obligatoriedad y mínimo y máximo,
estas solo se producirán cuando los componentes pierdan el foco.


  <div ng-controller="UserController">
    <form name="userForm" novalidate>
      <label for="name">Nombre</label><br />
      <input name="name" type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" required />
      <br />
      <label for="email">Email</label><br />
      <input name="email" type="email" ng-model="user.email" ng-model-options="{ updateOn: 'blur' }" required />
      <br />
      <label for="gender">Género</label><br />
      <input name="gender" type="radio" ng-model="user.gender" value="male" />Masculino
      <input name="gender" type="radio" ng-model="user.gender" value="female" />Femenino
      <br />
      <label for="age">Edad</label><br />
      <input name="age" type="number" ng-model="user.age" ng-model-options="{ updateOn: 'blur' }" min="0" max="150" />
      
      <br /><br />
      <input type="button" ng-click="reset()" value="Limpiar" />
      <input type="submit" ng-click="update()" value="Guardar" />
    </form>
    <pre>user = {{user | json}}</pre>
  </div>

  <script>
    angular.module('formLabs', [])
      .controller('UserController', ['$scope', function($scope) {
        $scope.user = {};

        $scope.update = function() {
          console.log($scope.user);
        };

        $scope.reset = function() {
          $scope.user = {};
        };

        $scope.reset();
      }]);
  </script>
</body>

Nos vamos acercando a una buena gestión de formularios solo nos quedan un par de cuestiones.


4.1. Estado del formulario y mensajes de validación.

Como hemos comentado anteriormente el hecho de indicar el nombre del formulario y el componente nos permite acceder
al estado interno del mismo y, con ello, poder comprobar si el formulario se ha submitido o el componente es inválido.

Basándonos en esos conceptos y consultando el API (el formulario expone una instancia de
FormController
y los componentes exponen una instancia de NgModelController)
podemos volver a modificar nuestro ejemplo para añadirle mensajes de error personalizados

<body ng-app="formLabs">
  <div ng-controller="UserController">
    <form name="userForm" novalidate>
      <label for="name">Nombre</label><br />
      <input name="name" type="text" ng-model="user.name" ng-model-options="{ updateOn: 'blur' }" required />
      <span class="messages" ng-show="userForm.$submitted || userForm.name.$touched">
        <span ng-show="userForm.name.$error.required">El campo es obligatorio.</span>
      </span>
      <br />
      <label for="email">Email</label><br />
      <input name="email" type="email" ng-model="user.email" ng-model-options="{ updateOn: 'blur' }" required />
      <span class="messages" ng-show="userForm.$submitted || userForm.email.$touched">
        <span ng-show="userForm.email.$error.required">El campo es obligatorio.</span>
        <span ng-show="userForm.email.$error.email">Formato de email incorrecto.</span>
      </span>
      <br />
      <label for="gender">Género</label><br />
      <input name="gender" type="radio" ng-model="user.gender" value="male" />Masculino
      <input name="gender" type="radio" ng-model="user.gender" value="female" />Femenino
      <br />
      <label for="age">Edad</label><br />
      <input name="age" type="number" ng-model="user.age" ng-model-options="{ updateOn: 'blur' }" min="0" max="150" />
      <span class="messages" ng-show="userForm.$submitted || userForm.age.$touched">
        <span ng-show="userForm.age.$error.max">La edad no puede exceder de 150.</span>
      </span>
      
      <br /><br />
      <input type="reset" ng-click="reset(userForm)" value="Limpiar" />
      <input type="submit" ng-click="update()" value="Guardar" ng-disabled="userForm.$invalid" />
    </form>
    <pre>user = {{user | json}}</pre>
  </div>

  <script>
    angular.module('formLabs', [])
      .controller('UserController', ['$scope', function($scope) {
        $scope.user = {};

        $scope.update = function() {
          console.log($scope.user);
        };

        $scope.reset = function(form) {
          $scope.user = {};
          if (form) {
            form.$setPristine();
            form.$setUntouched();
          }
        };

        $scope.reset();
      }]);
  </script>
</body>

Ni que decir tiene que esos literales deberían pasar por un filtro de traducción para que estén internacionalizados.

Adicionalmente a los mensajes de validación podemos comprobar que:

  • hemos añadido una condición de deshabilitación del botón de guardar si el contenido del formulario es inválido ng-disabled=»userForm.$invalid»
  • el evento de reset ahora inicializa el formulario para indicar que todos los componentes están limpios y no se han tocado.


4.2. Aplicando estilos.

No por verlo en último lugar es menos importante, puesto que «lo bien hecho, bien parece» y que menos que decorar
un componente de formulario con un estilo de inválido cuando existe algún error de validación sobre el mismo.

ngModel añade las siguientes clases a los componentes en función del estado interno del mismo, esas clases también son
aplicables al formulario.

  • ng-valid: el valor de la propiedad del modelo es válido
  • ng-invalid: el valor de la propiedad del modelo es inválido
  • ng-valid-[key]: donde key es el tipo de validación, asi ng-valid-required e indica que la validación para esa constraint es correcta
  • ng-invalid-[key]: idem que el anterior pero con el sentido contrario
  • ng-pristine: aún no se ha interactuado con el componente, en cuanto adquiera el foco, pasa a ng-touched
  • ng-dirty: se ha interactuado con el componente y se ha incluido algún valor en el mismo
  • ng-touched: se ha interactuado con el componente y se ha incluido o no algún valor en el mismo
  • ng-untouched: aún no se ha interactuado con el componente
  • ng-pending: hay validaciones que por su tipología se pueden hacer asíncroncas, con ng-pending podríamos controlar la promesas de las mismas

Con todo lo anterior y aplicando los siguientes estilos

<style type="text/css">

  .messages {
    color: #FA787E;
  }
  
  form.ng-submitted input.ng-invalid{
    border-color: #FA787E;
  }
  
  form input.ng-invalid.ng-touched {
    border-color: #FA787E;
  }
  
/*
  form input.ng-valid.ng-touched {
    border-color: #78FA89;
  }
*/  
</style>

podríamos disponer de una interfaz de formulario como la que se muestra en la captura:


5. Código fuente.

Que mejor que un plunker para que podáis trastear con el código fuente de este tutorial.


6. Referencias.


7. Conclusiones.

Hemos visto como trabajar de manera ordenada con formularios en AngularJS, el desconocimiento del framework y el conocimiento
de otros, tipo jQuery, o del propio lenguaje javascript nos pueden llevar a saltarnos el ciclo de vida de la gestión propia
de los formularios de AngularJS y a tener en definitiva una aplicación con un mal mantenimiento.

Nos quedaría ver cómo crear nuestros propios validadores asociados a directivas… stay tuned!

Un saludo.

Jose

2 COMENTARIOS

  1. Excelente aporte amigo, te felicito, solo dos inquietudes:
    ¿Para comparar campos ejemplo contraseñas, se puede hacer desde html, por medio de alguna expresión o es directo en el modulo? y ¿Es valido este mismo método para validar un select?

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad