Validación de forma angular

Una de las características más comunes en cualquier aplicación web es proporcionar un formulario a los usuarios para ingresar algunos datos. Utilizas formularios a diario para iniciar sesión, registrarte, realizar pedidos,...

Introducción

Una de las características más comunes en cualquier aplicación web es proporcionar un formulario a los usuarios para ingresar algunos datos. Utiliza formularios diariamente para iniciar sesión, registrarse, realizar pedidos, etc.

El procesamiento de las entradas de los usuarios antes de la validación puede tener graves consecuencias. Puede terminar almacenando datos no válidos como fecha, correo electrónico, edad, etc. incorrectos. También podría ser un problema de seguridad debido a ataques como [Secuencias de comandos entre sitios](https://en.wikipedia.org/wiki/Cross -site_scripting) (XSS).

La forma tradicional de validar formularios HTML es usando JavaScript o JQuery. Desafortunadamente, este enfoque requiere un montón de código.

Angular, al ser un marco completo, ha brindado un excelente soporte para validar las entradas de los usuarios y mostrar mensajes de validación. Tiene muchos validadores incorporados de uso común que puede aprovechar, o incluso puede escribir sus validadores personalizados.

Formularios en Angular

Un formulario Angular es un formulario HTML regular con pocas características adicionales. Para cada campo (entrada, radio, selección, etc.) en el formulario, necesitamos un objeto de la clase FormControl. El objeto FormControl da información sobre ese campo. Su valor, si el valor es válido, y si no es válido cuáles son los errores de validación, etc.

También proporciona el estado del campo, como tocado, sin tocar, sucio, prístino, etc.

De manera similar, un FormGroup es la colección de los objetos FormControl. Cada formulario Angular tiene al menos un FormGroup. Puede decidir tener múltiples ‘FormGroup’ en casos de uso, como separar el manejo de las secciones de detalles personales y detalles profesionales de un formulario de registro de usuario.

Todas las propiedades de un FormGroup (valid, error, etc.) también están disponibles para FormControl. Por ejemplo, la propiedad valid de un FormControl devolverá true si todas las instancias de FormControl son válidas.

Entonces, para agregar validación a un formulario Angular, necesitamos dos cosas:

  • Al menos un objeto FormGroup para el formulario
  • Un objeto FormControl para cada campo en el formulario

Hay dos formas diferentes de crear estos objetos de control. Podemos proporcionar algunas directivas en la plantilla del formulario y Angular puede crear tales controles bajo el capó para nosotros. Los formularios creados de esta manera se denominan formularios basados ​​en plantillas.

Si tenemos algunos casos de uso especiales y queremos más control sobre el formulario, podemos crear explícitamente tales objetos de control. Los formularios creados de esta manera se denominan formularios reactivos.

Formularios basados ​​en plantillas

En los formularios basados ​​en plantillas, aplicamos la directiva ngModel para cada campo de la plantilla. Angular crea un objeto FormControl debajo del capó para cada uno de estos campos y lo asocia con el campo respectivo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="form-group">
  <label for="name">Name</label>
  <input type="text" class="form-control" id="name"
         ngModel name="name">
</div>

<div class="form-group">
  <label for="username">Username</label>
  <input type="text" class="form-control" id="username"
         ngModel name="username">
</div>

Nota: Con ngModel, es necesario proporcionar el atributo name o definir FormControl como "independiente" en ngModelOptions; de lo contrario, Angular generará un error.

Además, en app.module.ts necesitaría agregar FormsModule a la matriz de importaciones:

1
2
3
4
5
6
7
import { FormsModule } from '@angular/forms';
// ...some other imports

imports: [
    //...some other imports
    FormsModule
]

Validación en formularios controlados por plantillas

Angular ha proporcionado algunos validadores integrados para validar casos de uso comunes. Para utilizar validadores integrados, deberá aplicar atributos de validación a cada campo de formulario en el que desee alguna validación. Estos atributos de validación son los mismos que los atributos de validación regulares de HTML5 como requerido, minlength, maxlength, etc. Bajo el hod, Angular ha proporcionado directivas para hacer coincidir estos atributos con las funciones de validación definidas en el marco Angular.

Cada vez que cambia el valor de FormControl, Angular genera una lista de errores de validación al ejecutar la validación. Si la lista está vacía, significa que es un estado válido; de lo contrario, es un estado no válido.

Digamos que queremos ponerle las siguientes validaciones:

  • Como los campos Nombre y Nombre de usuario tienen el atributo requerido, queremos mostrar un mensaje de validación si este campo se deja vacío.
  • El campo Nombre debe tener un valor cuyo minlegth y maxlength deben tener 2 y 30 caracteres respectivamente.
  • Si el nombre de usuario tiene espacios, muestra un mensaje de nombre de usuario no válido.

Para cada control de formulario en el que queremos agregar validación, debemos agregar los atributos de validación apropiados y exportar ngModel a una variable de plantilla local:

1
2
3
<input type="text" class="form-control" id="name"
    required maxlength="30" minlength="2"
    ngModel name="name" #name="ngModel">

En el ejemplo anterior, hemos utilizado los siguientes validadores integrados: requerido, minlength y maxlength.

Podemos usar la variable de plantilla nombre en la plantilla para verificar los estados de validación de los validadores usados:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<div *ngIf="name.invalid && (name.dirty || name.touched)"
    class="alert alert-danger">
  <div *ngIf="name.errors.required">
    Name is required.
  </div>
  <div *ngIf="name.errors.minlength">
    Name cannot be more than 30 characters long.
  </div>
  <div *ngIf="name.errors.minlength">
    Name must be at least 2 characters long.
  </div>
</div>

Como hemos usado una declaración condicional para representar el primer div, solo se mostrará si el estado del validador incorporado es inválido. Hemos explicado al comienzo de la sección cómo se determina el estado como válido o no válido.

De manera similar, los ‘div’s’ internos se mostrarán solo si la variable de plantilla ’nombre’ tiene una propiedad ’errores’ y la propiedad ’errores’ tiene una de las siguientes propiedades: ‘requerido’, ’longitud mínima’ y ’longitud máxima’ y la ID de valor de propiedad verdadero. Ya hemos discutido cómo la variable de plantilla se vincula a la directiva ngModel y recibe estas propiedades cada vez que hay algún cambio en el control de formulario y después de que Angular ejecuta la validación para ese campo.

Nota: es importante verificar los estados “sucio” y “tocado”, de lo contrario, se mostrará el mensaje de error la primera vez que se cargue la página, lo que es malo para la experiencia del usuario. Necesitamos que el mensaje de validación se muestre en una de las siguientes condiciones:

  • El usuario cambia algún valor, es decir, el campo está sucio (formControlObject.dirty)
  • El usuario usa la pestaña o hace clic para cambiar el enfoque a algún otro elemento, es decir, se tocó el campo (formControlObject.touched)

Si desea consultar una lista completa de los validadores integrados de Angular, puede seguir la [API de validadores] (https://angular.io/api/forms/Validators).

Escribir un validador personalizado

A veces, es posible que los validadores integrados no cubran su caso de uso exacto. En este caso, es posible que deba crear su función de validación personalizada.

Una función de validación implementa la interfaz ValidatorFn, lo que significa que debe tener la firma:

1
2
3
interface ValidatorFn {
    (control: AbstractControl): ValidationErrors | null
}

Los ValidationErrors deben ser un objeto que tenga uno o más pares clave-valor:

1
2
3
type ValidationErrors = {
    [key: string]: any;
};

La clave debe ser una cadena y se usa para indicar el tipo de error de validación, como correo electrónico no válido, requerido, etc. El valor puede ser cualquier cosa y se usa para proporcionar más información sobre el error de validación.

Para el ejemplo anterior, queremos escribir una función de validación personalizada que valide si no hay espacios en el nombre de usuario.

Si bien técnicamente podemos escribir esta función en cualquier parte de la aplicación, siempre es una buena práctica colocar todas las funciones de validación relacionadas dentro de una clase separada:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { ValidationErrors, AbstractControl } from '@angular/forms';

export class UserRegistrationFormValidators {
    static usernameShouldBeValid(control: AbstractControl): ValidationErrors | null {
        if ((control.value as string).indexOf(' ') >= 0) {
            return { shouldNotHaveSpaces: true }
        }

        // If there is no validation failure, return null
        return null;
    }
}

Nota: En este ejemplo, hemos devuelto true como el valor de la clave shouldNotHaveSpaces porque no necesitamos proporcionar ningún detalle. En algunos casos, es posible que deba proporcionar detalles, por ejemplo:

1
2
3
4
5
return { maxlengthExceeded: {
        maxLength: 20,
        actual: control.value.length
    }
}

A continuación, podemos usar esta función de validación UserRegistrationFormValidators.usernameShouldBeValid para el control de formulario username en nuestro formulario basado en plantillas:

1
2
3
4
5
6
7
<div class="form-group">
  <label for="username">Username</label>
  <input type="text" class="form-control" id="username"
         required
         UserRegistrationFormValidators.usernameShouldBeValid
         [(ngModel)]="person.username" name="username">
</div>

Formularios reactivos {#formularios reactivos}

En formularios reactivos, creamos objetos FormControl explícitamente en el componente de esa plantilla. Aquí está el formulario HTML regular sin ninguna directiva o validación ngModel:

1
2
3
4
5
6
7
8
9
<div class="form-group">
  <label for="name">Name</label>
  <input type="text" class="form-control" id="name">
</div>

<div class="form-group">
  <label for="username">Username</label>
  <input type="text" class="form-control" id="username">
</div>

Supongamos que queremos convertir nuestro formulario basado en plantillas del ejemplo anterior en un formulario reactivo.

Para esto, primero, necesitamos crear explícitamente FormGroup y FormControls para cada campo en el componente de la plantilla:

1
2
3
4
form = new FormGroup({
    'name': new FormControl(),
    'username': new FormControl(),
})

Nota: Como se discutió anteriormente, un formulario puede tener más de un FormGroup. En este caso, podemos tener una estructura anidada:

1
2
3
4
5
registrationForm = new FormGroup({
    'personalDetailsForm': new FormGroup({
        'name': new FormControl()
    })
})

Puedes leer más sobre FormGroup en la documentación angular.

Permítanme llamar su atención de nuevo a nuestro caso de uso.

A continuación, debemos asociar estos objetos FormControl a los campos en el formulario HTML.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<form [formGroup]="registrationForm">
<div class="form-group">
  <label for="name">Name</label>
  <input type="text" class="form-control" id="name"
         [formControlName]="name">
</div>

<div class="form-group">
  <label for="username">Username</label>
  <input type="text" class="form-control" id="username"
         [formControlName]="username">
</div>
<form>

Aquí aplicamos la directiva formGroup y la asociamos con el objeto FormGroup registrationForm que creamos en el componente. También asociamos la directiva formControlName con los respectivos objetos FormControl name y username.

Nota: Las directivas para construir formularios reactivos están definidas en ReactiveFormsModule. Entonces, si obtiene un error como:

1
Can't bind to formGroup

...entonces deberías comprobar si has importado ese ReactiveFormsModule en tu módulo principal app.module.ts.

Validaciones en formularios reactivos

En formularios reactivos, no pasamos la directiva ngModel y tampoco usamos atributos de validación de HTML5. Especificamos validadores mientras creamos los objetos del FormControl en el propio componente.

Aquí está la firma de la clase FormControl:

1
2
3
4
5
class FormControl extends AbstractControl {
    constructor(formState: any = null, validatorOrOpts?: ValidatorFn | AbstractControlOptions | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[])

    // ...
}

Como podemos ver, el primer parámetro es el estado inicial del control que se puede mantener vacío, es decir, ''. El segundo parámetro es ValidatorFn.

Para agregar las funciones de validación incorporadas para un FormControl, podemos pasarle el ValidatorFn apropiado. Para el siguiente ejemplo, hemos utilizado los siguientes validadores integrados requerido, minLength y maxLength -:

1
2
3
4
5
6
7
8
registrationForm = new FormGroup({
    'name': new FormControl('Enter your name', [
        Validators.required,
        Validators.minLength(2),
        Validators.maxLength(30)
    ]),
    'username': new FormControl('', Validators.required),
})

Nota: Deberá importar Validadores en el componente.

Tenga en cuenta también que, a diferencia de los formularios basados ​​en plantillas, no utilizamos los atributos de validación. Usamos el ValidatorFn respectivo como Validators.required, Validators.minLength(2), etc. Su editor de código puede proporcionar autocompletado para todos los ValidatorFn en el momento en que escribe Validators seguido de un punto ..

Podemos volver a la plantilla y escribir mensajes de validación:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<form [formGroup]="registrationForm">
<div class="form-group">
  <label for="name">Name</label>
  <input type="text" class="form-control" id="name"
         [formControlName]="name">
  <div *ngIf="registrationForm.get('name').invalid && (registrationForm.get('name').dirty || registrationForm.get('name').touched)"
    class="alert alert-danger">
    <div *ngIf="registrationForm.get('name').errors.required">
       Name is required.
    </div>
    <div *ngIf="registrationForm.get('name').errors.minlength">
       Name cannot be more than 30 characters long.
    </div>
    <div *ngIf="registrationForm.get('name').errors.minlength">
       Name must be at least 2 characters long.
    </div>
  </div>
</div>

<div class="form-group">
  <label for="username">Username</label>
  <input type="text" class="form-control" id="username"
         [formControlName]="username">
</div>
<form>

Validadores personalizados para formularios reactivos

Necesitamos escribir la función de validación personalizada de la misma manera que lo hicimos para la sección de formulario Dirigida por plantilla. Podemos usar la misma función de validación personalizada UserRegistrationFormValidators.usernameShouldBeValid en el componente para el formulario reactivo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
registrationForm = new FormGroup({
    'name': new FormControl('Enter your name', [
        Validators.required,
        Validators.minLength(2),
        Validators.maxLength(30)
    ]),
    'username': new FormControl('', [
        Validators.required,
        UserRegistrationFormValidators.usernameShouldBeValid
    ]),
})

Conclusión

En este tutorial, exploramos las dos formas diferentes de manejar las entradas de los usuarios: formularios controlados por plantillas y reactivos. Aprendimos cómo poner validación en ambos tipos de formularios. Y finalmente, también escribimos nuestra función de validación personalizada y la incluimos con los validadores integrados.

Como podemos ver, Angular tiene un gran soporte para formularios y proporciona algunas características útiles internas para validar formularios. Proporcionar cada función con formas angulares está más allá del alcance de este tutorial. Puede leer la documentación angular para obtener información completa. a.