Publicada:

Angular 14 - Ejemplo de validación de formulario basado en plantillas (Template-Driven Forms)

Ejemplo construido con Angular 14.2.12

Otras versiones disponibles:

Este es un ejemplo rápido de cómo implementar la validación de formularios en Angular 14 con formularios controlados por plantillas.

Formularios basados en plantillas (Template-Driven Forms) frente a formularios reactivos (Reactive Forms)

Con los formularios controlados por plantillas, casi todo se define en la plantilla (como sugiere el nombre). Se accede a los datos del formulario a través de una variable de plantilla en el elemento del formulario (por ejemplo, <form #f="ngForm" ...>), se usa la directiva ngModel para registrar controles de entrada de formulario con el formulario principal (p. ej., <input name="email" ngModel ... >), las directivas se utilizan para especificar reglas de validación en cada elemento de entrada (p. ej., <input ... required email>) y las propiedades del modelo, como los mensajes de error de validación (p. ej., email.errors) se accede a través de variables de plantilla declaradas en el elemento de entrada (p. ej., <input ... #email="ngModel">).

Alternativamente, los formularios reactivos usan un enfoque basado en modelos donde usted define sus campos de formulario y reglas de validación en el componente como un FormGroup y lo vincula al elemento de formulario en la plantilla con un atributo de enlace de datos (por ejemplo, <form ... [formGroup]="registerForm">). Para ver el mismo tutorial creado con formularios reactivos, consulte Angular 14 - Ejemplo de Validación de Formularios Reactivos (Reactive Forms).

Ejemplo de formulario de registro de Angular 14

El ejemplo es un formulario de registro simple con campos bastante estándar para título, nombre, apellido, fecha de nacimiento, correo electrónico, contraseña, confirmación de contraseña y una casilla de verificación de aceptar términos y condiciones. El formulario define las siguientes reglas de validación:

  • Campos obligatorios: todos los campos son obligatorios, incluida la casilla de verificación.
  • Validación de fecha: el campo Fecha de nacimiento debe contener una fecha válida.
  • Validación de correo electrónico: el correo electrónico debe tener un formato válido.
  • Longitud mínima de la contraseña: la contraseña debe contener al menos 6 caracteres.
  • Las contraseñas deben coincidir: los campos de confirmación de contraseña y contraseña deben coincidir. Esto se implementa mediante un validador MustMatch personalizado y se adjunta al formulario con una directiva [mustMatch] personalizada.

Validar en el envío del formulario

Configuré el formulario para que se valide al enviar en lugar de tan pronto como se cambie cada campo (tocado/sucio), esto se implementa usando la propiedad f.submitted de la variable de plantilla de formulario (#f="ngForm"), la propiedad es igual a true después de enviar el formulario por primera vez.

Diseñado con Bootstrap 5

La aplicación de inicio de sesión de ejemplo está diseñada con el CSS de Bootstrap 5.2, para obtener más información sobre Bootstrap consulte https://getbootstrap.com/docs/5.2/getting-started/introduction/.

Código en GitHub

El proyecto está disponible en github en https://github.com/cornflourblue/angular-14-template-driven-form-validation-example.

Aquí está en acción: (Ver en StackBlitz en https://stackblitz.com/edit/angular-14-template-driven-form-validation-example)


Componente de aplicación que maneja datos de formulario

El componente no hace mucho cuando se usan formularios controlados por plantillas de Angular, ya que los campos de formulario y los validadores se definen en la plantilla del componente.

El método onSubmit() se llama con la variable de plantilla NgForm cuando se envía el formulario, está vinculado al elemento de formulario en la plantilla usando la sintaxis de vinculación de eventos Angular ((ngSubmit)="onSubmit(f)"). Si es válido, se muestra una simple alert de javascript con los valores ingresados en el formulario.

import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({ selector: 'app-root', templateUrl: 'app.component.html' })
export class AppComponent {
    onSubmit(f: NgForm) {
        // stop here if form is invalid
        if (f.invalid) {
            return;
        }

        alert('SUCCESS!! :-)\n\n' + JSON.stringify(f.value, null, 4));
    }
}


Plantilla de componente de aplicación con reglas de validación de formulario

La plantilla del componente de la aplicación contiene todo el marcado html para mostrar el formulario de registro de ejemplo en el navegador. El evento de envío de formulario está vinculado al método onSubmit() del componente de la aplicación con el enlace de evento (ngSubmit)="onSubmit(f)". La variable de plantilla #f="ngForm" crea una instancia de FormGroup para proporcionar acceso a los datos del formulario y al estado de validación.

Campos de entrada registrados con el formulario principal

Cada campo de entrada se registra con el formulario usando la directiva ngModel. En el contexto de un formulario principal, la directiva se puede usar sin enlace de datos ([()]), en su lugar, el control se registra usando el atributo de entrada name y el formulario se mantiene los datos sincronizados.

Validadores y directivas

La validación se implementa mediante funciones de validación que se adjuntan a los campos mediante directivas. La mayoría de los validadores utilizados vienen como parte del marco Angular (required, minlength, email, pattern). Se creó un validador personalizado (mustMatch) para verificar la contraseña y los campos de confirmación de contraseña coinciden.

Validación de entrada de fecha

La directiva pattern se usa con una expresión regular (^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$) para validar el formato del campo de fecha (aaaa-mm-dd).

<!-- main app container -->
<div class="card m-3">
    <h5 class="card-header text-center">Angular 14 Template-Driven Form Validation</h5>
    <div class="card-body">
        <form #f="ngForm" (ngSubmit)="onSubmit(f)" [mustMatch]="['password', 'confirmPassword']" novalidate>
            <div class="row">
                <div class="col mb-3">
                    <label class="form-label">Title</label>
                    <select name="title" ngModel #title="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && title.invalid }" required>
                        <option value=""></option>
                        <option value="Mr">Mr</option>
                        <option value="Mrs">Mrs</option>
                        <option value="Miss">Miss</option>
                        <option value="Ms">Ms</option>
                    </select>
                    <div *ngIf="f.submitted && title.errors" class="invalid-feedback">
                        <div *ngIf="title.errors.required">Title is required</div>
                    </div>
                </div>
                <div class="col-5 mb-3">
                    <label class="form-label">First Name</label>
                    <input type="text" name="firstName" ngModel #firstName="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && firstName.invalid }" required>
                    <div *ngIf="f.submitted && firstName.errors" class="invalid-feedback">
                        <div *ngIf="firstName.errors.required">First Name is required</div>
                    </div>
                </div>
                <div class="col-5 mb-3">
                    <label class="form-label">Last Name</label>
                    <input type="text" name="lastName" ngModel #lastName="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && lastName.invalid }" required>
                    <div *ngIf="f.submitted && lastName.errors" class="invalid-feedback">
                        <div *ngIf="lastName.errors.required">Last Name is required</div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col mb-3">
                    <label class="form-label">Date of Birth</label>
                    <input type="date" name="dob" ngModel #dob="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && dob.invalid }" required pattern="^\d{4}\-(0[1-9]|1[012])\-(0[1-9]|[12][0-9]|3[01])$">
                    <div *ngIf="f.submitted && dob.errors" class="invalid-feedback">
                        <div *ngIf="dob.errors.required">Date of Birth is required</div>
                        <div *ngIf="dob.errors.pattern">Date of Birth must be a valid date in the format YYYY-MM-DD</div>
                    </div>
                </div>
                <div class="col mb-3">
                    <label class="form-label">Email</label>
                    <input type="text" name="email" ngModel #email="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && email.invalid }" required email>
                    <div *ngIf="f.submitted && email.errors" class="invalid-feedback">
                        <div *ngIf="email.errors.required">Email is required</div>
                        <div *ngIf="email.errors.email">Email must be a valid email address</div>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col mb-3">
                    <label class="form-label">Password</label>
                    <input type="password" name="password" ngModel #password="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && password.invalid }" required minlength="6">
                    <div *ngIf="f.submitted && password.errors" class="invalid-feedback">
                        <div *ngIf="password.errors.required">Password is required</div>
                        <div *ngIf="password.errors.minlength">Password must be at least 6 characters</div>
                    </div>
                </div>
                <div class="col mb-3">
                    <label class="form-label">Confirm Password</label>
                    <input type="password" name="confirmPassword" ngModel #confirmPassword="ngModel" class="form-control" [ngClass]="{ 'is-invalid': f.submitted && confirmPassword.invalid }" required>
                    <div *ngIf="f.submitted && confirmPassword.errors" class="invalid-feedback">
                        <div *ngIf="confirmPassword.errors.required">Confirm Password is required</div>
                        <div *ngIf="confirmPassword.errors.mustMatch">Passwords must match</div>
                    </div>
                </div>
            </div>
            <div class="form-check mb-3">
                <input type="checkbox" name="acceptTerms" id="acceptTerms" ngModel #acceptTerms="ngModel" class="form-check-input" [ngClass]="{ 'is-invalid': f.submitted && acceptTerms.invalid }" required>
                <label for="acceptTerms" class="form-check-label">Accept Terms & Conditions</label>
                <div *ngIf="f.submitted && acceptTerms.errors" class="invalid-feedback">Accept Ts & Cs is required</div>
            </div>
            <div class="text-center">
                <button class="btn btn-primary me-1">Register</button>
                <button class="btn btn-secondary" type="reset">Cancel</button>
            </div>
        </form>
    </div>
</div>


Validador MustMatch personalizado

El validador MustMatch personalizado se usa en el ejemplo para validar que ambos campos de contraseña coincidan. Pero se puede usar para validar que cualquier par de campos coincida (por ejemplo, campos de correo electrónico y confirmación de correo electrónico).

Funciona ligeramente diferente a un validador típico, por lo general un validador espera un parámetro de control de entrada mientras que este espera un grupo de formularios porque está validando dos entradas en lugar de una.

Además, en lugar de devolver un error que se adjuntaría al grupo de formulario principal, el validador llama a matchingControl.setErrors() para adjuntar el error al campo de confirmación de contraseña. Pensé que esto tenía más sentido porque el error de validación se muestra debajo del campo confirmPassword en la plantilla.

import { AbstractControl } from '@angular/forms';

// custom validator to check that two fields match
export function MustMatch(controlName: string, matchingControlName: string) {
    return (group: AbstractControl) => {
        const control = group.get(controlName);
        const matchingControl = group.get(matchingControlName);

        if (!control || !matchingControl) {
            return null;
        }

        // return if another validator has already found an error on the matchingControl
        if (matchingControl.errors && !matchingControl.errors.mustMatch) {
            return null;
        }

        // set error on matchingControl if validation fails
        if (control.value !== matchingControl.value) {
            matchingControl.setErrors({ mustMatch: true });
        } else {
            matchingControl.setErrors(null);
        }
        return null;
    }
}


Directiva mustMatch personalizada

La directiva personalizada [mustMatch] envuelve el validador personalizado MustMatch para que podamos adjuntarlo al formulario. Se requiere una directiva de validación personalizada cuando se usan formularios basados en plantillas porque no tenemos acceso directo al FormGroup como en los formularios reactivos.

La directiva implementa la interfaz Validator y se registra con el proveedor NG_VALIDATORS para que Angular sepa que es una directiva de validación personalizada.

Acepta una matriz con los nombres de 2 controles de formulario que deben coincidir para que pase la validación del formulario, p. [mustMatch]="['field1', 'field2']" validará que field1 y field2 contienen el mismo valor; de lo contrario se establecerá un error de validación en el field2. Puede ver su uso en la etiqueta de formulario de la plantilla de aplicación anterior.

import { Directive, Input } from '@angular/core';
import { NG_VALIDATORS, Validator, ValidationErrors, FormGroup } from '@angular/forms';

import { MustMatch } from './must-match.validator';

@Directive({
    selector: '[mustMatch]',
    providers: [{ provide: NG_VALIDATORS, useExisting: MustMatchDirective, multi: true }]
})
export class MustMatchDirective implements Validator {
    @Input('mustMatch') mustMatch: string[] = [];

    validate(formGroup: FormGroup): ValidationErrors | null {
        return MustMatch(this.mustMatch[0], this.mustMatch[1])(formGroup);
    }
}


Módulo de la aplicación

No hay mucho que hacer en el módulo de la aplicación aparte de las cosas estándar, lo principal que debe recordar para usar formularios basados en plantillas en Angular es importar el FormsModule (import { FormsModule } from '@angular/forms';) e inclúyalo en la matriz de imports del decorador @NgModule. Importe también la directiva de validación personalizada (import { MustMatchDirective } from './_helpers';) e inclúyala en la matriz declarations del decorador @NgModule.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { MustMatchDirective } from './_helpers';

@NgModule({
    imports: [
        BrowserModule,
        FormsModule
    ],
    declarations: [
        AppComponent,
        MustMatchDirective
    ],
    bootstrap: [AppComponent]
})
export class AppModule { }

 


Suscríbete o Sígueme para actualizaciones

Suscríbete a mi canal de YouTube o sígueme en Twitter, Facebook o GitHub para recibir notificaciones cuando publique contenido nuevo.

Aparte de la codificación...

Actualmente estoy intentando viajar por Australia en motocicleta con mi esposa Tina en un par de Royal Enfield Himalayan. Puedes seguir nuestras aventuras en YouTube, Instagram y Facebook.


¿Necesita Ayuda Angular 14?

Buscar fiverr para encontrar ayuda rápidamente de desarrolladores Angular 14 experimentados.