Los formularios basados en plantillas se construyen utilizando la estructura de plantillas de Angular y aprovechan el sistema de detección de cambios de Angular para gestionar los estados del formulario. En este enfoque, se utiliza la directiva ngModel para vincular los elementos del formulario a propiedades en el componente, permitiendo la comunicación bidireccional entre la vista y el modelo.
Ejemplo de un formulario básico
<form #miFormulario="ngForm" (ngSubmit)="onSubmit(miFormulario)">
<label for="nombre">Nombre:</label>
<input type="text" id="nombre" name="nombre" ngModel required>
<button type="submit" [disabled]="miFormulario.invalid">Enviar</button>
</form>
En este ejemplo:
#miFormulario=”ngForm” crea una referencia al formulario que permite acceder a su estado.
ngModel se utiliza para enlazar el campo de entrada nombre a la propiedad del modelo.
El botón de envío está deshabilitado hasta que el formulario sea válido.
Simplicidad: La sintaxis es más directa y fácil de entender para formularios sencillos.
Menos código: Requiere menos configuración en comparación con los formularios reactivos, ya que gran parte de la lógica se maneja en la plantilla.
Ideal para formulario simples: Perfecto para formularios que no requieren validaciones complejas o un manejo avanzado de estados.
Angular ofrece varias directivas para realizar validaciones de forma sencilla. Las validaciones pueden ser de varios tipos, incluyendo requeridas, patrones o longitudes.
Ejemplo de validaciones
<input type="text" id="email" name="email" ngModel required email>
<div *ngIf="miFormulario.controls.email?.invalid && (miFormulario.controls.email?.touched || miFormulario.controls.email?.dirty)">
<small *ngIf="miFormulario.controls.email?.errors?.required">El email es requerido.</small>
<small *ngIf="miFormulario.controls.email?.errors?.email">El formato del email es inválido.</small>
</div>
El manejo de eventos en formularios basados en plantillas es sencillo. Se puede utilizar la directiva (ngSubmit) para manejar el evento de envío del formulario.
Ejemplo de manejo de eventos
onSubmit(form: NgForm) {
console.log('Formulario enviado:', form.value);
}
Usar validaciones: Siempre que sea posible, agregar validaciones para mejorar la experiencia del usuario y garantizar la calidad de los datos.
Manejar el estado del formulario: Utilizar las propiedades de estado (touched, dirty, valid, invalid) para mejorar la interacción del usuario.
Evitar lógica compleja en la plantilla: Mantener la lógica en el componente siempre que sea posible para que la plantilla sea más limpia y fácil de entender.
Los formularios reactivos se construyen utilizando el módulo ReactiveFormsModule, que permite crear formularios utilizando una API programática. En lugar de vincular la plantilla directamente a los modelos a través de ngModel, se utiliza la clase FormGroup para representar el estado del formulario y FormControl para cada campo de entrada.
Ejemplo de un formulario reactivo
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-mi-formulario',
template: `
<form [formGroup]="miFormulario" (ngSubmit)="onSubmit()">
<label for="nombre">Nombre:</label>
<input id="nombre" formControlName="nombre">
<div *ngIf="miFormulario.get('nombre').invalid && miFormulario.get('nombre').touched">
<small *ngIf="miFormulario.get('nombre').errors.required">El nombre es requerido.</small>
</div>
<button type="submit" [disabled]="miFormulario.invalid">Enviar</button>
</form>
`
})
export class MiFormularioComponent {
miFormulario: FormGroup;
constructor(private fb: FormBuilder) {
this.miFormulario = this.fb.group({
nombre: ['', Validators.required]
});
}
onSubmit() {
console.log('Formulario enviado:', this.miFormulario.value);
}
}
Control total: Proporciona un mayor control sobre el estado del formulario y la validación, permitiendo una lógica más compleja.
Escalabilidad: Facilita la creación de formularios complejos y dinámicos, ideales para aplicaciones grandes.
Pruebas sencillas: La estructura basada en clases permite realizar pruebas unitarias más efectivas y fáciles de entender.
Cambio de datos: Soporta la programación reactiva, lo que significa que los cambios en los datos pueden ser escuchados y gestionados fácilmente.
Un formulario reactivo está compuesto por varios elementos clave:
FormGroup: Representa un grupo de controles. Se puede anidar para crear estructuras de formulario más complejas.
FormControl: Representa un único control de entrada. Puede tener validaciones y un estado asociado (touched, dirty, valid).
FormArray: Representa un conjunto de controles en un array, útil para formularios que requieren listas dinámicas.
Las validaciones se pueden definir directamente en los controles o como un conjunto de validaciones en el grupo. Angular proporciona varios validadores integrados, y también se pueden crear validadores personalizados.
Ejemplo de validaciones
this.miFormulario = this.fb.group({
nombre: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]]
});
El manejo de eventos en formularios reactivos se realiza a través de métodos en el componente. Por ejemplo, se pueden usar observables para reaccionar a los cambios en los valores del formulario.
Ejemplo de manejo de eventos
this.miFormulario.get('nombre').valueChanges.subscribe(value => {
console.log('Nombre cambiado:', value);
});
Angular ofrece varios tipos de validaciones, que se pueden clasificar en:
Validaciones requeridas: Aseguran que un campo no esté vacío.
Validaciones de formato: Verifican que el valor ingresado cumpla con un formato específico (como correos electrónicos o números).
Validaciones de longitud: Comprobar que la longitud del texto esté dentro de un rango especificado.
Validaciones personalizadas: Permiten crear reglas de validación a medida según las necesidades de la aplicación.
<form #miFormulario="ngForm" (ngSubmit)="onSubmit(miFormulario)">
<label for="email">Email:</label>
<input type="email" id="email" name="email" ngModel required email>
<div *ngIf="miFormulario.controls.email?.invalid && (miFormulario.controls.email?.touched || miFormulario.controls.email?.dirty)">
<small *ngIf="miFormulario.controls.email?.errors?.required">El email es requerido.</small>
<small *ngIf="miFormulario.controls.email?.errors?.email">El formato del email es inválido.</small>
</div>
<button type="submit" [disabled]="miFormulario.invalid">Enviar</button>
</form>
En formularios reactivos, las validaciones se definen en el componente usando la clase FormBuilder para crear un FormGroup con validadores asociados a cada FormControl.
Ejemplo de validaciones en formularios reactivos
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-mi-formulario',
template: `
<form [formGroup]="miFormulario" (ngSubmit)="onSubmit()">
<label for="nombre">Nombre:</label>
<input id="nombre" formControlName="nombre">
<div *ngIf="miFormulario.get('nombre').invalid && miFormulario.get('nombre').touched">
<small *ngIf="miFormulario.get('nombre').errors.required">El nombre es requerido.</small>
<small *ngIf="miFormulario.get('nombre').errors.minlength">El nombre debe tener al menos 3 caracteres.</small>
</div>
<button type="submit" [disabled]="miFormulario.invalid">Enviar</button>
</form>
`
})
export class MiFormularioComponent {
miFormulario: FormGroup;
constructor(private fb: FormBuilder) {
this.miFormulario = this.fb.group({
nombre: ['', [Validators.required, Validators.minLength(3)]]
});
}
onSubmit() {
console.log('Formulario enviado:', this.miFormulario.value);
}
}
Angular permite la creación de validaciones personalizadas, lo que brinda mayor flexibilidad. Estas validaciones se implementan como funciones que devuelven un objeto de error si la validación falla.
Ejemplo de validación personalizada
import { AbstractControl, ValidationErrors } from '@angular/forms';
export function nombreValidator(control: AbstractControl): ValidationErrors | null {
const valid = /^[A-Za-z]+$/.test(control.value);
return valid ? null : { invalidNombre: true };
}
this.miFormulario = this.fb.group({
nombre: ['', [Validators.required, nombreValidator]]
});
Es importante proporcionar retroalimentación al usuario en caso de errores. Angular permite mostrar mensajes de error de manera condicional, basándose en el estado de validación de cada control.
Ejemplo de mensajes de error
<div *ngIf="miFormulario.get('nombre').invalid && miFormulario.get('nombre').touched">
<small *ngIf="miFormulario.get('nombre').errors.required">El nombre es requerido.</small>
<small *ngIf="miFormulario.get('nombre').errors.invalidNombre">El nombre solo puede contener letras.</small>
</div>
Validaciones visuales: Proporcionar mensajes claros y visibles para ayudar a los usuarios a corregir errores.
Uso de validaciones: Utilizar tanto validaciones integradas como personalizadas para cubrir diferentes casos de uso.
Manejo de estados: Usar estados como touched y dirty para mostrar errores solo después de que el usuario ha interactuado con el campo.
Objetivo
Desarrollar un formulario de registro que incluya campos para nombre, email y contraseña, utilizando tanto formularios basados en plantillas como formularios reactivos.
npm install -g @angular/cli
ng new formulario-registro
cd formulario-registro
npm install @angular/forms
formulario-registro/
│
├── src/
│ ├── app/
│ │ ├── registro-reactivo/
│ │ │ ├── registro-reactivo.component.ts
│ │ │ ├── registro-reactivo.component.html
│ │ │ ├── registro-reactivo.component.css
│ │ │ └── registro-reactivo.component.spec.ts
│ │ │
│ │ ├── registro-plantilla/
│ │ │ ├── registro-plantilla.component.ts
│ │ │ ├── registro-plantilla.component.html
│ │ │ ├── registro-plantilla.component.css
│ │ │ └── registro-plantilla.component.spec.ts
│ │ │
│ │ ├── app.component.ts
│ │ ├── app.component.html
│ │ ├── app.module.ts
│ │ └── ...
│ └── ...
└── ...
ng generate component registro-reactivo
ng generate component registro-plantilla
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-registro-reactivo',
templateUrl: './registro-reactivo.component.html',
})
export class RegistroReactivoComponent {
miFormulario: FormGroup;
constructor(private fb: FormBuilder) {
this.miFormulario = this.fb.group({
nombre: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
contrasena: ['', [Validators.required, Validators.minLength(6)]],
});
}
onSubmit() {
console.log('Formulario Reactivo enviado:', this.miFormulario.value);
}
}
<form [formGroup]="miFormulario" (ngSubmit)="onSubmit()">
<label for="nombre">Nombre:</label>
<input id="nombre" formControlName="nombre">
<div *ngIf="miFormulario.get('nombre').invalid && miFormulario.get('nombre').touched">
<small>El nombre es requerido.</small>
</div>
<label for="email">Email:</label>
<input id="email" formControlName="email">
<div *ngIf="miFormulario.get('email').invalid && (miFormulario.get('email').touched || miFormulario.get('email').dirty)">
<small *ngIf="miFormulario.get('email').errors?.required">El email es requerido.</small>
<small *ngIf="miFormulario.get('email').errors?.email">El formato del email es inválido.</small>
</div>
<label for="contrasena">Contraseña:</label>
<input id="contrasena" type="password" formControlName="contrasena">
<div *ngIf="miFormulario.get('contrasena').invalid && miFormulario.get('contrasena').touched">
<small *ngIf="miFormulario.get('contrasena').errors?.required">La contraseña es requerida.</small>
<small *ngIf="miFormulario.get('contrasena').errors?.minlength">La contraseña debe tener al menos 6 caracteres.</small>
</div>
<button type="submit" [disabled]="miFormulario.invalid">Registrar</button>
</form>
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
@Component({
selector: 'app-registro-plantilla',
templateUrl: './registro-plantilla.component.html',
})
export class RegistroPlantillaComponent {
onSubmit(form: NgForm) {
console.log('Formulario de Plantilla enviado:', form.value);
}
}
<form #miFormulario="ngForm" (ngSubmit)="onSubmit(miFormulario)">
<label for="nombre">Nombre:</label>
<input type="text" id="nombre" name="nombre" ngModel required>
<div *ngIf="miFormulario.controls.nombre?.invalid && miFormulario.controls.nombre?.touched">
<small>El nombre es requerido.</small>
</div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" ngModel required email>
<div *ngIf="miFormulario.controls.email?.invalid && (miFormulario.controls.email?.touched || miFormulario.controls.email?.dirty)">
<small *ngIf="miFormulario.controls.email?.errors?.required">El email es requerido.</small>
<small *ngIf="miFormulario.controls.email?.errors?.email">El formato del email es inválido.</small>
</div>
<label for="contrasena">Contraseña:</label>
<input type="password" id="contrasena" name="contrasena" ngModel required minlength="6">
<div *ngIf="miFormulario.controls.contrasena?.invalid && miFormulario.controls.contrasena?.touched">
<small *ngIf="miFormulario.controls.contrasena?.errors?.required">La contraseña es requerida.</small>
<small *ngIf="miFormulario.controls.contrasena?.errors?.minlength">La contraseña debe tener al menos 6 caracteres.</small>
</div>
<button type="submit" [disabled]="miFormulario.invalid">Registrar</button>
</form>
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule } from '@angular/forms';
import { FormsModule } from '@angular/forms'; // Importar para formularios basados en plantillas
import { AppComponent } from './app.component';
import { RegistroReactivoComponent } from './registro-reactivo/registro-reactivo.component';
import { RegistroPlantillaComponent } from './registro-plantilla/registro-plantilla.component';
@NgModule({
declarations: [
AppComponent,
RegistroReactivoComponent,
RegistroPlantillaComponent
],
imports: [
BrowserModule,
ReactiveFormsModule,
FormsModule // Añadir aquí
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
<h1>Formulario de Registro</h1>
<h2>Registro con Formulario Reactivo</h2>
<app-registro-reactivo></app-registro-reactivo>
<h2>Registro con Formulario Basado en Plantillas</h2>
<app-registro-plantilla></app-registro-plantilla>
ng serve