Formularios de plantilla

<< Router Formularios reactivos >>

Los formularios suelen ser una parte importante de una aplicación web (o cualquier aplicación gráfica). Nos permiten crear nuevos datos, actualizarlos, crear un formulario login, etc. Angular tiene funcionalidades integradas para gestionar formularios, validarlos y ayudar a mostrar errores e información al usuario sobre sus campos.

En cualquier componente en el que deseemos utilizar la validación de formularios o directivas como NgModel (cualquier cosa relacionada con formularios), debemos importar el módulo FormsModule (importar directamente directivas como NgModel no es una opción porque no están creadas como standalone).

Vincular campos: NgModel

Hemos visto la directiva ngModel como un ejemplo de enlace bidireccional de datos. Esto significa que un elemento <input /> que tiene esta directiva tendrá su valor vinculado a una propiedad del componente. El valor puede cambiarse tanto desde código, modificando el valor de la propiedad, como interactuando con el elemento del formulario.

Con los controles de formulario vinculados con la directiva ngModel, podemos utilizar atributos de validación HTML5 como required, min, max, pattern, minlength y maxlength. Angular tiene una serie de validadores integrados que ejecuta cuando la directiva ngModel detecta estos atributos. Este proceso de validación añaden clases CSS automáticamente al elemento para poder jugar con los estilos.

Vamos a ver un ejemplo con el campo de la descripción de producto en el componente product-form que habíamos creado anteriormente. El campo será obligatorio (required) y además tendrá una longitud mínima y máxima.

Así son las clases que se generan cuando el valor del campo aún no ha sido editado:

Componentes anidados

Y estas son las que tiene el campo cuando ha sido modificado y es válido:

Componentes anidados

Estas son las clases que genera Angular para los campos de los formularios, por si queremos asociarles algún tipo de estilo con CSS.

EstadoClase (true)Clase (false)
Campo visitadong-touchedng-untouched
Campo actualizadong-dirtyng-pristine
Campo validadong-validng-invalid

También podemos crear una referencia al objeto de la directiva ngModel. Una referencia se crea con un atributo que comienza con '#'. Luego podemos utilizar esa referencia para acceder a algunas de sus propiedades y verificar cosas como si ha cambiado (dirty), si está validado (valid), o su valor (value).

Componentes anidados

Podríamos añadir clases CSS a partir de la directiva ngClass y las propiedades del objeto ngModel asociado al campo de formulario. En este caso vamos a añadir las clases is-valid o is-invalid de Bootstrap.

Componentes anidados

Validando el campo de la imagen

En nuestro ejemplo de productos, el campo de la imagen no está vinculado a ninguna propiedad del producto, ya que mediante el evento (change), la transformamos a base64 cuando se selecciona un archivo. Momento en el cual se la asignamos al campo correspondiente del producto. Teníamos la propiedad fileName asociada, pero la podemos eliminar, ya que no vamos a vaciar los valores del formulario en ningún momento. De esta manera, vamos a ver como incluir un campo que no está vinculado a ninguna propiedad del componente en la validación.

Si queremos que el campo entre dentro de la validación del formulario, debemos asignarle la directiva ngModel también, solo que en este caso, no está vinculada a ninguna propiedad. Esto permite utilizar validadores como required y cambiar el aspecto del campo cuando tenga un valor válido.

Evento de cambio de valor: ngModelChange

Finalmente, podemos reaccionar a cambios para filtrar, por ejemplo, lo que estamos escribiendo en el campo, dividiendo nuestra directiva ngModel en [ngModel] y (ngModelChange). $event representa el último valor introducido en el campo. Por ejemplo, si queremos que el valor del campo esté siempre en mayúsculas:

Método para gestionar las clases de validación

Para intentar repetir menos código en nuestro HTML, sobre todo con ngClass, vamos a crear un método en el componente que recibe el objeto ngModel, la clase CSS a asignar cuando no sea válido, y la clase cuando sea válido. Devolverá un objeto que le asignaremos a la directiva ngClass.

Mensajes de validación

Para mostrar mensajes de error cuando un campo no es válido podríamos usar CSS (a partir de las clases que genera Angular), o @if para detectar si el estado del ngModel es invalid y mostrar el error. Para nuestro ejemplo con Bootstrap, vamos a crear un elemento con la clase invalid-feedback junto al campo del formulario. Este elemento solo se muestra cuando el campo tiene la clase is-invalid. Más información.

Además, podemos consultar el campo errors del objeto ngModel. Este campo será null cuando no haya errores y contendrá un objeto con el error cuando detecte alguno. La propiedad del error se llamará como el error producido (required, minlength, ...) y puede tener a su vez más información interna.

Componentes anidados

Crear validadores personalizados

Puede registrarse un nuevo validador en la aplicación creando una directiva que implemente la interfaz Validator. La clase de esta directiva tendrá un método llamado validate que recibirá el campo de formulario actual para validar. Vamos a crear un validador que verifique que la fecha de un campo debe ser posterior a otra fecha que recibirá como parámetro de entrada (@Input).

ng g directive validators/min-date

Para que funcione como validador, la tenemos que registrar en la colección de validadores de Angular (NG_VALIDATORS). Esto lo hacemos con la propiedad providers en el decorador de la directiva.

Ahora importamos la directiva en nuestro componente (product-form) y la usamos con el campo del formulario correspondiente.

Componentes anidados

Validar campos agrupados

Si necesitamos crear un validador que requiera acceder a más de un campo o control de formulario, podemos utilizar la directiva ngModelGroup en un elemento padre. Esto creará un objeto FormGroup que agrupará todos los campos situados dentro de dicho elemento.

Vamos a crear un validador que compruebe que en un grupo de campos de tipo checkbox, al menos hay uno seleccionado. Este es un ejemplo independiente que no se puede aplicar al ejemplo productos que hemos estado creando. Primero crearíamos la directiva:

ng g d validators/one-checked

La función de validación en este caso recibirá un objeto del tipo FormGroup. Este objeto tendrá los valores de los campos en un objeto, cuyas propiedades son los nombres de los campos. Por ejemplo: {days0: true, days1: true, days2: false, …}'. Dentro del método comprobaremos que si no hay al menos un valor a true (checked), devolvemos un error.

Controlar envío del formulario

Se puede crear una referencia en la plantilla al objeto de la directiva ngForm que contiene información de validación global del formulario. A partir de dicha referencia, podemos consultar la validación global de todo el formulario y desactivar, por ejemplo, el botón de envío mientras el formulario no esté 100% validado.

Vincular formulario en el componente: NgForm

Si queremos consultar el estado de la validación o los valores internos del formulario, también podemos vincular el objeto ngForm desde el la clase del componente. Para ello creamos una propiedad de tipo NgForm vinculada a la referencia creada en la plantilla (#addForm) con el decorador @ViewChild.

Vincular campos en el componente: NgModel

De la misma manera que podemos vincular el objeto de la directiva NgForm, también se puede vincular el objeto NgModel asociado a un campo del formulario. En este caso indicando la referencia creada en la plantilla asociada a ese objeto.

Un posible uso de esta directiva sería vigilar los cambios de valor en el componente. Para ello tiene una propiedad, valueChanges, que contiene un observable al que suscribirse y que emitirá el valor del campo cada vez que este cambia. De esta manera podemos programar una acción a ejecutar cuando cambie dicho valor. Es importante tener en cuenta que los objetos referenciados con @ViewChild no están disponibles hasta como mínimo la ejecución de ngAfterViewInit en el ciclo de vida.

Otras posibilidades de @ViewChild y @ViewChildren

Se puede establecer una referencia a cualquier elemento, ya sea un elemento nativo HTML o un componente Angular (o directiva en el caso de NgModel o NgForm). Para crear una referencia a un elemento, la creamos sin igualarla a nada (empezando por '#'). En el componente, esta referencia es un objeto del tipo ElementRef, pero se puede acceder al elemento nativo (similar a getElementById) con la propiedad nativeElement.

Otra opción, si se quieren referenciar todos los componentes de un tipo (o directivas) que están en la plantilla es usar la clase del componente. Si hay más de un componente de ese tipo en la plantilla, se puede usar @ViewChildren que devuelve una colección (tipo QueryList) con esos elementos.

Importante: Si esos componentes están detrás de una instrucción de flujo de control como @if o @for, inicialmente (ngAfterViewInit) la colección QueryList estará sin elementos. Los elementos en la colección referenciada se van actualizando en tiempo real.

<< Router Formularios reactivos >>