Template-driven forms
Los formularios template-driven representan la forma más sencilla y directa de trabajar con formularios en Angular. Este enfoque utiliza directivas en el template HTML para gestionar el estado y comportamiento del formulario, permitiendo una experiencia de desarrollo similar a los formularios HTML tradicionales pero con toda la funcionalidad reactiva de Angular.
¿Qué son los formularios template-driven?
Los formularios template-driven se basan en el uso de directivas que se aplican directamente en el HTML del template. Angular se encarga automáticamente de crear y gestionar el modelo del formulario en segundo plano, sin que necesitemos definir explícitamente la estructura en el componente TypeScript.
Este enfoque es especialmente útil para formularios simples a medianos, donde la lógica de validación no es extremadamente compleja y preferimos mantener la configuración cerca del template visual.
Configuración de FormsModule
Para utilizar formularios template-driven, necesitamos importar FormsModule en nuestro componente standalone:
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-user-form',
template: `
<form>
<input type="text" name="username" placeholder="Nombre de usuario">
<button type="submit">Enviar</button>
</form>
`,
imports: [FormsModule]
})
export class UserFormComponent {
// Lógica del componente
}
El FormsModule proporciona todas las directivas necesarias para trabajar con formularios template-driven, incluyendo ngModel
, ngForm
, y las funcionalidades de validación básicas.
La directiva ngForm automática
Una característica fundamental de los formularios template-driven es que Angular aplica automáticamente la directiva ngForm a cualquier elemento <form>
en el template. Esto significa que no necesitamos declararla explícitamente:
@Component({
template: `
<!-- Angular aplica ngForm automáticamente -->
<form>
<input type="email" name="email" placeholder="Email">
<input type="password" name="password" placeholder="Contraseña">
<button type="submit">Iniciar sesión</button>
</form>
`,
imports: [FormsModule]
})
export class LoginFormComponent { }
La directiva ngForm
se encarga de:
- Crear el modelo del formulario automáticamente
- Rastrear el estado de validación y modificaciones
- Gestionar el envío del formulario
- Coordinar todos los controles dentro del formulario
Variables de plantilla para acceso al formulario
Podemos acceder al estado y propiedades del formulario utilizando variables de plantilla. Esto nos permite interactuar con el formulario directamente desde el template:
@Component({
template: `
<form #contactForm="ngForm">
<input type="text" name="name" placeholder="Nombre">
<input type="email" name="email" placeholder="Email">
<button type="submit">Contactar</button>
<!-- Información de depuración -->
<div>
<p>Formulario válido: {{ contactForm.valid }}</p>
<p>Formulario modificado: {{ contactForm.dirty }}</p>
<p>Valores: {{ contactForm.value | json }}</p>
</div>
</form>
`,
imports: [FormsModule]
})
export class ContactFormComponent { }
La variable #contactForm="ngForm"
nos da acceso a propiedades importantes como:
valid/invalid
: Estado de validación del formulariodirty/pristine
: Si el formulario ha sido modificadotouched/untouched
: Si el usuario ha interactuado con el formulariovalue
: Objeto con todos los valores de los controles
Diferencias con formularios reactivos
Los formularios template-driven se diferencian de los formularios reactivos en varios aspectos clave:
- 1. Definición: Los template-driven se definen en el template, los reactivos en el componente
- 2. Complejidad: Los template-driven son ideales para casos simples, los reactivos para lógica compleja
- 3. Validación: Los template-driven usan validadores HTML y directivas, los reactivos usan funciones TypeScript
- 4. Testing: Los reactivos son más fáciles de testear unitariamente
// Template-driven: configuración en el template
@Component({
template: `
<form #form="ngForm">
<input type="text" name="username" required>
</form>
`
})
export class SimpleFormComponent { }
Para este curso de iniciación, nos enfocaremos en los formularios template-driven por su simplicidad y curva de aprendizaje más suave.
Estructura básica de un formulario
Un formulario template-driven típico sigue esta estructura:
@Component({
selector: 'app-product-form',
template: `
<form #productForm="ngForm">
<div>
<label for="name">Nombre del producto:</label>
<input
type="text"
id="name"
name="name"
placeholder="Ingrese el nombre">
</div>
<div>
<label for="price">Precio:</label>
<input
type="number"
id="price"
name="price"
placeholder="0.00">
</div>
<button type="submit">Guardar producto</button>
</form>
`,
imports: [FormsModule]
})
export class ProductFormComponent { }
Cada control del formulario debe tener un atributo name
único, que Angular utiliza para identificar y rastrear el control dentro del modelo del formulario.
Los formularios template-driven proporcionan una base sólida para la mayoría de casos de uso en aplicaciones web, combinando la familiaridad de HTML con la reactividad de Angular.
Two-way binding con ngModel
El two-way binding con ngModel
es la característica más distintiva de los formularios template-driven. Esta funcionalidad permite sincronizar automáticamente los datos entre el modelo del componente y los controles del formulario, creando un flujo bidireccional de información que simplifica enormemente la gestión del estado.
¿Qué es ngModel?
La directiva ngModel
combina property binding y event binding en una sola sintaxis elegante. Utilizando la notación [(ngModel)]
, conocida como "banana in a box", establecemos una conexión bidireccional entre una propiedad del componente y un control de formulario.
@Component({
selector: 'app-user-profile',
template: `
<form #profileForm="ngForm">
<input
type="text"
name="username"
[(ngModel)]="user.username"
placeholder="Nombre de usuario">
<p>Hola, {{ user.username }}!</p>
</form>
`,
imports: [FormsModule]
})
export class UserProfileComponent {
user = {
username: 'usuario123'
};
}
En este ejemplo, cualquier cambio en el input se refleja inmediatamente en user.username
, y viceversa.
Sintaxis y funcionamiento
El two-way binding con [(ngModel)]
es equivalente a combinar property binding y event binding manualmente:
// Estas dos sintaxis son equivalentes:
// Sintaxis corta con ngModel
<input [(ngModel)]="nombre" name="nombre">
// Sintaxis expandida (equivalente)
<input
[ngModel]="nombre"
(ngModelChange)="nombre = $event"
name="nombre">
La sintaxis corta es mucho más limpia y expresiva, por lo que es la forma recomendada de trabajar con formularios template-driven.
Diferentes tipos de inputs
La directiva ngModel
funciona con todos los tipos de controles de formulario HTML. Veamos ejemplos prácticos con cada tipo:
Input de texto:
@Component({
template: `
<form #form="ngForm">
<input
type="text"
name="firstName"
[(ngModel)]="person.firstName"
placeholder="Nombre">
<input
type="email"
name="email"
[(ngModel)]="person.email"
placeholder="correo@ejemplo.com">
</form>
`,
imports: [FormsModule]
})
export class PersonFormComponent {
person = {
firstName: '',
email: ''
};
}
Input numérico:
@Component({
template: `
<form #productForm="ngForm">
<input
type="number"
name="price"
[(ngModel)]="product.price"
placeholder="Precio"
min="0"
step="0.01">
<input
type="range"
name="rating"
[(ngModel)]="product.rating"
min="1"
max="5">
<p>Precio: ${{ product.price }}</p>
<p>Calificación: {{ product.rating }}/5</p>
</form>
`,
imports: [FormsModule]
})
export class ProductFormComponent {
product = {
price: 0,
rating: 3
};
}
Checkbox:
@Component({
template: `
<form #preferencesForm="ngForm">
<label>
<input
type="checkbox"
name="notifications"
[(ngModel)]="settings.notifications">
Recibir notificaciones
</label>
<label>
<input
type="checkbox"
name="newsletter"
[(ngModel)]="settings.newsletter">
Suscribirse al boletín
</label>
<div>
<p>Notificaciones: {{ settings.notifications ? 'Activadas' : 'Desactivadas' }}</p>
<p>Boletín: {{ settings.newsletter ? 'Suscrito' : 'No suscrito' }}</p>
</div>
</form>
`,
imports: [FormsModule]
})
export class SettingsComponent {
settings = {
notifications: true,
newsletter: false
};
}
Select dropdown:
@Component({
template: `
<form #orderForm="ngForm">
<select name="country" [(ngModel)]="order.country">
<option value="">Selecciona un país</option>
<option value="es">España</option>
<option value="mx">México</option>
<option value="ar">Argentina</option>
<option value="co">Colombia</option>
</select>
<select name="priority" [(ngModel)]="order.priority">
<option value="low">Baja</option>
<option value="medium">Media</option>
<option value="high">Alta</option>
</select>
<p>País seleccionado: {{ order.country }}</p>
<p>Prioridad: {{ order.priority }}</p>
</form>
`,
imports: [FormsModule]
})
export class OrderFormComponent {
order = {
country: 'es',
priority: 'medium'
};
}
Radio buttons:
@Component({
template: `
<form #surveyForm="ngForm">
<fieldset>
<legend>¿Cómo nos conociste?</legend>
<label>
<input
type="radio"
name="source"
value="search"
[(ngModel)]="survey.source">
Motor de búsqueda
</label>
<label>
<input
type="radio"
name="source"
value="social"
[(ngModel)]="survey.source">
Redes sociales
</label>
<label>
<input
type="radio"
name="source"
value="friend"
[(ngModel)]="survey.source">
Recomendación
</label>
</fieldset>
<p>Fuente: {{ survey.source }}</p>
</form>
`,
imports: [FormsModule]
})
export class SurveyComponent {
survey = {
source: 'search'
};
}
Textarea y controles de texto multilínea
@Component({
template: `
<form #messageForm="ngForm">
<textarea
name="message"
[(ngModel)]="feedback.message"
placeholder="Escribe tu mensaje aquí..."
rows="4"
cols="50">
</textarea>
<p>Caracteres: {{ feedback.message.length }}</p>
</form>
`,
imports: [FormsModule]
})
export class FeedbackComponent {
feedback = {
message: ''
};
}
Consideraciones importantes
Atributo name obligatorio:
Todos los controles que usan ngModel
deben tener un atributo name
único. Angular utiliza este nombre para registrar el control en el formulario:
<!-- ✅ Correcto -->
<input type="text" name="username" [(ngModel)]="user.username">
<!-- ❌ Error: falta el atributo name -->
<input type="text" [(ngModel)]="user.username">
Inicialización de propiedades:
Es importante inicializar las propiedades que vas a enlazar con ngModel
para evitar errores:
export class FormComponent {
// ✅ Correcto: propiedades inicializadas
user = {
name: '',
email: '',
age: 0,
active: false
};
// ❌ Problemático: propiedades no inicializadas
// user: any;
}
Actualización en tiempo real
Una de las ventajas más evidentes del two-way binding es la sincronización inmediata. Cualquier cambio en el input se refleja instantáneamente en el modelo y en otras partes del template:
@Component({
template: `
<form #calculatorForm="ngForm">
<input
type="number"
name="quantity"
[(ngModel)]="order.quantity"
placeholder="Cantidad">
<input
type="number"
name="price"
[(ngModel)]="order.price"
placeholder="Precio unitario">
<div class="summary">
<p>Cantidad: {{ order.quantity }}</p>
<p>Precio unitario: ${{ order.price }}</p>
<p><strong>Total: ${{ order.quantity * order.price }}</strong></p>
</div>
</form>
`,
imports: [FormsModule]
})
export class CalculatorComponent {
order = {
quantity: 1,
price: 0
};
}
El two-way binding con ngModel
proporciona una experiencia de desarrollo fluida y reactiva, permitiendo crear interfaces de usuario dinámicas con muy poco código. Esta funcionalidad es la base sobre la cual construiremos validaciones y funcionalidades más avanzadas en las siguientes lecciones.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Angular
Documentación oficial de Angular
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué son los formularios template-driven y su funcionamiento básico.
- Aprender a configurar FormsModule para usar formularios en Angular.
- Utilizar la directiva ngForm y variables de plantilla para gestionar el estado del formulario.
- Implementar el two-way binding con ngModel para sincronizar datos entre el modelo y la vista.
- Manejar diferentes tipos de controles de formulario con ngModel y entender la importancia del atributo name.