Al finalizar este tutorial el estudiante estará en capacidad de implementar un formulario básico en Angular para crear un recurso de la aplicación.
En este tutorial vamos a crear una aplicación Angular con un componente para crear un formulario.
Específicamente, utilizará la librería Angular de formularios reactivos (Reactive Forms) para definir un formulario en un componente de creación de un recurso. Este formulario tendrá la definición de los elementos que se quieren capturar en la interacción con el usuario así como sus validaciones básicas. Igualmente, utilizará el formulario en el template HTML y desplegará mensajes de error y mensaje de confirmación cuando el usuario hace un submit del formulario.
El resultado del tutorial es una aplicación que tiene un formulario básico (ver Figura 1). Hay tres campos:
xyz@xyz.com
).Figura 1. Formulario de creación de cliente |
Para realizar este taller Ud. debe tener claro:
Utilizando Angular-cli desde VSCode cree un nuevo proyecto Angular.
Sobre la carpeta app de su proyecto seleccione la opción de Angular Generator (angular-cli) y allí Módulo. Defina el nombre de su módulo como client
.
Vaya al archivo app.module.ts
y agregue el nuevo módulo. Debe hacer dos cosas:
imports
del decorador del módulo, el nombre del módulo nuevo.Vaya a la carpeta del módulo client
, clic derecho Angular Generator/Componente.
Dele el nombre client-create
.
Angular-cli crea la declaración del componente en el módulo. Sin embargo, se debe agregar una línea con el atributo exports
para que este componente se pueda ver desde la aplicación principal.
La librería que vamos a utilizar para manejar los formularios se llama ReactiveFormsModule
y se debe incluir e importar en el módulo donde se definen componentes que tendrán formularios. En este tutorial, nuestro módulo es ClientModule
.
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { ClientCreateComponent } from './client-create/client-create.component';
@NgModule({
imports: [
CommonModule, ReactiveFormsModule
],
declarations: [ClientCreateComponent],
exports:[ClientCreateComponent]
})
export class ClientModule { }
Vamos a llamar el componente de crear un cliente utilizando su selector desde el componente principal.
En el archivo del componente principal app.component.html
borre lo que hay y pegue el selector entre tags de apertura y cierre, tal como se ve en la Figura 2.
Figura 2. Componente principal |
Angular cuenta con librerías para manejar los formularios. Estas librerías permiten crear objetos que contienen los campos en el formulario y que serán desplegados en la vista. La librería se encarga de mantener la relación entre los campos de entrada (input
) que despliega la vista y los datos en el componente.
Estas librerías debemos importarlas en el componente de creación del recurso.
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
...
En nuestro ejemplo, vamos a implementar un formulario para crear un nuevo cliente. En el siguiente código del componente vamos a declarar una variable clientForm
de tipo FormGroup
. También vamos a incluir en el constructor una variable formBuilder de tipo FormBuilder y una variable toastr de tipo ToastrService.
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ToastrService } from "ngx-toastr";
import { Client } from '../client';
@Component({
selector: "app-client-create",
templateUrl: "./client-create.component.html",
styleUrls: ["./client-create.component.css"]
})
export class ClientCreateComponent implements OnInit {
clientForm: FormGroup;
constructor(private formBuilder: FormBuilder,
private toastr: ToastrService
) {}
...
}
Un objeto formulario es de tipo FormGroup
y contiene un conjunto de objetos FormControl
donde cada uno representa un campo del formulario para que el usuario ingrese valores.
Vamos a inicializar la forma clientForm
con los elementos que la componen. Esto lo hacemos en el método ngOnInit del componente. Por cada campo de entrada, definimos un nombre y sus propiedades de validación si es que las tiene:
ngOnInit() {
this.clientForm = this.formBuilder.group({
name: ["", [Validators.required, Validators.minLength(2)]],
address: ["", Validators.required],
email: ["", [Validators.required, Validators.email]]
});
En el formulario clientForm
, estamos definiendo tres campos:
name
, su valor inicial es vacío y tiene dos validaciones: la primera dice que el campo es obligatorio y la segunda dice que el valor debe tener una longitud mínima de 2 caracteres. address
, su valor inicial es vacío y tiene una validación que indica que el campo es obligatorio.email
, su valor inicial es vacío, tiene dos validaciones que indican que el campo es obligatorio y que debe seguir el formato de los email.La validaciones significan que si cualquiera de esas validaciones no se cumple, por ejemplo, el campo address
no tiene valor, el formulario es inválido. En este ejemplo solo estamos utilizando validaciones predefinidas (ver enlace https://angular.io/api/forms/Validators) . También podemos definir nuestras propias validaciones (ver enlace https://angular.io/guide/form-validation#custom-validators) .
La vista del componente, es decir el código HTML, va en el archivo client-create.component.html
.
El formulario que vamos a construir es el que se detalla en la Figura 3.
Figura 3. Ejemplo de formulario que se va a construir |
Este formulario es un elemento HTML form
que contiene los tres elementos inputs
con sus respectivos label
y el código para desplegar los mensajes de error debajo de cada elemento, si no se cumplen las reglas de validación. Antes de cerrar la etiqueta form
, se incluye tanto el botón de submit
para crear el cliente, como el botón de cancel
para cancelar el formulario. Queremos, además, que el botón de submit
esté activo sólo si el formulario es válido.
Vamos a explicar el HTML gradualmente sin detenernos en el detalle de los estilos. La Figura 4 muestra la organización del HTML. Existe una etiqueta form
que contiene, por cada campo en el formulario, un grupo de etiquetas para el despliegue y las validaciones. Al final está el código para mostrar los botones.
Figura 4. Organización del HTML |
La etiqueta form
contiene la referencia al modelo del formulario, es decir, a la variable clientForm
que se creó en la clase del componente. También contiene la directiva Angular ngSubmit
que tiene por valor la expresión que será evaluada cuando el usuario hace clic en el botón de submit
. En este caso, preguntamos si el formulario es válido y se llama la función createClient(clientForm.value)
. Esta función debemos definirla en la clase del componente. El detalle de este código se muestra a continuación:
<form [formGroup]="clientForm" (ngSubmit)="!clientForm.invalid && createClient(clientForm.value)">
...
</form>
Veamos ahora el código asociado con los botones que van antes de cerrar la etiqueta
</form>.
El botón para crear el cliente, está deshabilitado si el formulario no es válido, lo que se representa con el código [disabled]="!clientForm.valid".
Ese botón es de tipo type="submit
" lo que implica que cuando el usuario le hace clic se ejecutará el ngSubmit
del formulario. En este caso se llamará a crear el cliente con los valores recolectados en los campos que se encuentran en clientForm.value
.
El botón de cancelar tiene una directiva denominada click
que invoca la función cancelCreation()
que también debe estar definida en la clase del componente.
<button type="submit" class="btn btn-primary" [disabled]="!clientForm.valid">Create</button> <button type="button" class="btn btn-danger ml-3" (click)="cancelCreation()">Cancel</button>
Por cada campo tenemos un label, una etiqueta input
y una etiqueta por cada validación que fue definida en la configuración del formulario en la clase del componente.
La siguiente figura muestra lo que se despliega si el usuario ingresa mal la información en todos los campos de acuerdo con las reglas definidas. El nombre tiene menos de 2 letras, la dirección no tiene valor y el email no tiene el formato correcto. Note que en la Figura 5, Create
no está habilitado porque el formulario no es válido.
Figura 5. Formulario inválido |
Veamos un ejemplo con el campo name
que fue definido de la siguiente forma en el componente, con dos validaciones:
this.clientForm = this.formBuilder.group({
name: ["", [Validators.required, Validators.minLength(2)]],
...
});
El HTML correspondiente es el siguiente: el label, el input y las dos validaciones:
<div class="form-group mx-sm-3 mb-2">
<label for="name">
Name
</label>
<input novalidate id="name" class="form-control" formControlName="name" placeholder="Your Full Name">
<div class="alert alert-danger alert-dismissible fade show"
*ngIf="clientForm.get('name').hasError('required') && clientForm.get('name').touched">
Name required
</div>
<div class="alert alert-danger alert-dismissible fade show"
*ngIf="clientForm.get('name').hasError('minlength')">
Name too short
</div>
</div>
Para la etiqueta input
, tenemos que el significado de cada atributo es el siguiente:
|
|
|
|
|
|
|
|
|
|
Procesar una validación consiste en definir un condicional utilizando la directiva de Angular *ngIf
para consultar si hay el error y en ese caso, desplegar el mensaje al usuario.
En este ejemplo queremos saber si hay error en la validación sobre la longitud del texto.:
*ngIf="clientForm.get('name').hasError('minlength')
Veamos el código:
<div class="alert alert-danger alert-dismissible fade show"
*ngIf="clientForm.get('name').hasError('minlength') && clientForm.get('name').touched">
Name too short
</div>
También se agrega clientForm.get('name').touched
para saber si el campo fue efectivamente tocado por el usuario.
De nuestro formulario las consultas al error son:
Definición en el componente | *ngIf en el HTML |
|
|
|
|
|
|
<div class="container-fluid">
<div class="col-4 p-3">
<div class="h4" i18n>Customer Creation</div>
<form
[formGroup]="clientForm"
(ngSubmit)="!clientForm.invalid && createClient(clientForm.value)"
>
<div class="form-group mx-sm-3 mb-2">
<label for="name"> Name* </label>
<input
novalidate
id="name"
class="form-control"
formControlName="name"
placeholder="Your Full Name"
/>
<div
class="alert alert-danger alert-dismissible fade show"
*ngIf="
clientForm.get('name').hasError('required') &&
clientForm.get('name').touched
"
>
Name required
</div>
<div
class="alert alert-danger alert-dismissible fade show"
*ngIf="
clientForm.get('name').hasError('minlength') &&
clientForm.get('name').touched
"
>
Name too short. It should be at least 2 characters
</div>
</div>
<div class="form-group mx-sm-3 mb-2">
<label for="address"> Address* </label>
<input
id="address"
type="text"
class="form-control"
formControlName="address"
placeholder="Your Address"
/>
<div
class="alert alert-danger alert-dismissible fade show"
*ngIf="
clientForm.get('address').hasError('required') &&
clientForm.get('address').touched
"
>
Address required
</div>
</div>
<div class="form-group mx-sm-3 mb-2">
<label for="email"> Email* </label>
<input
id="email"
type="text"
class="form-control"
formControlName="email"
placeholder="Your Email"
/>
<div
class="alert alert-danger alert-dismissible fade show"
*ngIf="
clientForm.get('email').hasError('required') &&
clientForm.get('email').touched
"
>
Email required
</div>
<div
class="alert alert-danger alert-dismissible fade show"
*ngIf="
clientForm.get('email').touched &&
clientForm.get('email').hasError('email')
"
>
Incorrect email format. It must include @
</div>
</div>
<div class="row m-3 small">
<p>* Required fields</p>
</div>
<div class="row m-3">
<button
type="submit"
class="btn btn-success"
[disabled]="!clientForm.valid"
>
Create
</button>
<button
type="button"
class="btn btn-danger ml-3"
(click)="cancelCreation()"
>
Cancel
</button>
</div>
</form>
</div>
</div>
Cuando el formulario es válido y el usuario hace clic en el botón Create, estamos invocando el método createClient(clientForm.value) que debe estar definido en la clase del componente. En nuestro tutorial tenemos el siguiente código, que imprime el cliente que se va a crear en la consola, llama a showSucess()
que utiliza el toastr
y resetea el formulario. Entre comentarios lo que sería el código completo si llamamos el servicio que invoca el HttpClient
de Angular.
createClient(newClient: Client) {
// Process checkout data here
console.warn("el cliente fue creado", newClient);
this.showSuccess(newClient);
//-----------------------------------------------------------------
// this.clientService.createClient(newClient).subscribe(client => {
// this.clientes.push(client);
// this.showSuccess(newClient);
// });
//------------------------------------------------------------------
this.clientForm.reset();
}
El resultado final utilizando el toastr
es el que se presenta en la Figura 6.
Figura 6. Ejemplo del toastr |
Cuando el usuario cancela debemos decidir si queremos enviarle un mensaje al usuario utilizando el toastr
, debemos resetear el formulario y decidir a dónde queremos navegar.
En este tutorial que solo tiene el formulario no navegamos a ningún lado, solo reseteamos el formulario.
cancelCreation() {
console.log("Cancelando ...");
this.clientForm.reset();
}