Template-driven Forms
Los formularios de plantilla o Template-driven Forms se gestionan principalmente desde la plantilla por medio directivas como ngModel. Las validaciones también se gestionan desde la plantilla.
En general son útiles para formularios no muy complejos donde no necesitamos una gran adaptabilidad (por ejemplo cambiar validadores en función de ciertos parámetros) o un enfoque más reactivo ante cambios, aunque se puede hacer de forma similar a los formularios reactivos si referenciamos las directivas ngForm y ngModel desde el componente apoyándonos en los View Queries (viewChild y viewChildren).
Para utilizar formularios de plantilla, importaremos en nuestro componente el módulo FormsModule. A partir de aquí, todos los formularios presentes en el componente pasan a ser gestionados por la directiva NgForm (se añade automáticamente).
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 (directivas) que podemos añadir junto a la directiva ngModel. Este proceso de validación añade 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.
Estas son las clases que se añaden al campo cuando el valor del mismo aún no ha sido editado:

Y estas son las que tiene cuando ha sido modificado y cumple con los validadores:

Veamos qué clases que genera Angular para los campos de los formularios, por si queremos asociarles estilos CSS en función de su estado.

También podemos crear una referencia al objeto de la directiva ngModel con el prefijo '#'. Luego podremos utilizar dicha referencia para acceder a algunas de sus propiedades y verificar el estado del campo: si ha sido visitado (touched) si ha cambiado (dirty), si está validado (valid), o su valor (value).

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.

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 solo nos interesa el valor transformado a base64 y no el nombre del archivo. Sin embargo, nos interesa mantener la directiva ngModel aunque no esté vinculada a ninguna propiedad en el componente. Esto permite utilizar los validadores que hemos visto antes.
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 valor actual del campo. Por ejemplo, si quisiéramos que el valor del campo estuviera siempre en mayúsculas:
Directiva para gestionar las clases de validación
Para intentar repetir menos código en nuestro HTML, sobre todo con ngClass, vamos a crear una directiva a la que le podamos pasar una clase a poner cuando el campo sea válido y otra cuando no lo sea. En ambos casos se comprueba que el campo ha sido visitado (ha recibido el foco y lo ha perdido).
ng g d shared/directives/validation-classes
En el selector de la directiva vamos a poner que debe tener el atributo ngModel para asegurarnos de que tiene esa directiva. De esta manera podemos inyectarla con la función inject y así comprobar el estado de la validación cuando queramos.
La directiva es algo compleja si lo queremos hacer bien, ya que se van a tener en cuenta 3 dependencias que pueden hacer cambiar la clase asignada al campo:
- El campo pierde el foco (producido por el evento blur).
- El valor del campo cambia. Para vigilarlo, generamos una señal a partir del observable valueChanges de NgModel.
- Cambian las clases que recibe la directiva en algún momento
Lo bueno es que una vez hecha, es sencilla de utilizar y se puede reutilizar en cualquier parte de nuestra aplicación o en otras aplicaciones.
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.

Crear validadores personalizados
Validación de una fecha mínima
Puede registrarse un nuevo validador para formularios de plantilla 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 shared/directives/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.

Validar al menos un checkbox marcado
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 shared/directives/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 haya al menos un valor marcado a true (checked).
Validar que emails coinciden
Para comprobar, por ejemplo, si 2 campos tienen el mismo valor, podríamos agruparlos como hemos hecho en el ejemplo anterior y crear un validador de grupo. En este caso crearemos un validador llamado equalValues que recibirá un array con los nombres de los 2 campos a comparar.
ng g d shared/directives/equal-values
Para agrupar estos campos, si no queremos crear un elemento HTML que los agrupe en la plantilla, podemos recurrir a ng-container, que no se renderizará en el DOM, pero sirve para agrupar otros elementos en estos casos.
En este caso no podemos utilizar la directiva validationClasses que hemos creado porque las clases de validación dependen del error asociado a la directiva ngModelGroup y no a la directiva ngModel del campo.
Si no queremos crear un ngModelGroup para agrupar campos, siempre podemos utilizar el validador directamente en el formulario (ngForm). El formulario se puede utilizar igual ya que ambas directivas contienen un objeto FormGroup que representa un grupo de controles de formulario. Lo malo de esta solución es que el validador se ejecutaría cada vez que cambie un campo del formulario y no cuando cambien solo los campos afectados.
Una tercera opción sería crear un validador a nivel de campo y pasarle el valor del otro campo para que compruebe que son iguales. Vamos a crear un validador llamado sameValue para probarlo.
ng g d shared/directives/same-value
La parte compleja de esta solución es que Angular solo chequea los validadores del campo cuando el valor de este cambia. Sin embargo, en este caso se debería recalcular también cuando cambia el valor del otro campo. Para eso tenemos una función que Angular nos pasa en el método registerOnValidatorChange (opcional) del validador y que podemos llamar a propósito cuando queramos que Angular compruebe la validación del campo.
En este caso tampoco nos vale la directiva validationClasses para este campo, ya que si el cambio de valor no viene del campo donde está la directiva, no recalcula la clase CSS que debe ponerle.
Controlar envío del formulario
Se puede crear una referencia en la plantilla al objeto de la directiva ngForm que Angular inyecta a los formularios del componente cuando importamos FormsModule. Esta directiva controla varios aspectos del formulario y además guarda los valores del mismo en forma de objeto y la información de validación global. A partir de dicha referencia, podemos consultar la validación del formulario y desactivar, por ejemplo, el botón de envío mientras el formulario no esté 100% validado.
Más adelante veremos otras posibles estrategias para controlar diferentes aspectos del formulario, como el envío si es válido, desde el componente, bien sea con formularios reactivos en lugar de lo visto aquí, o referenciando las directivas ngModel o ngForm en el componente utilizando viewChild.
Esto mismo se puede hacer con los formularios de plantilla referenciando el objeto NgModel, NgModelGroup o NgForm en el componente, como veremos más adelante con View Queries.