Reactive Forms es un módulo que nos provee Angular para definir formularios de una forma inmutable y reactiva. Por medio de este módulo podemos construir controles dentro de un formulario y asociarlos a las etiquetas HTML del template sin necesidad de usar explícitamente un ngModel
. A diferencia de Angular Forms, Reactive Forms hace uso de Observables y Streams para mantener trackeada los datos del formulario, lo cual nos permite interceptarla y transformarla por medio de operadores usando RxJs.
Para empezar a usar Reactive Forms necesitamos importar e incluir el módulo ReactiveFormsModule
:
@NgModule({
declarations: [
SignupComponent
],
imports: [
CommonModule,
ReactiveFormsModule,
InputModule,
ButtonModule
],
exports: [
SignupComponent
]
})
export class SignupModule { }
Anatomía de un formulario reactivo
Una vez que tenemos importado el módulo, podemos usarlo en nuestro componente y template. La estructura de nuestro formulario con Reactive Forms tiene la siguiente forma.
<form [formGroup]="myForm" (onSubmit)="doSomething()">
<input formControlName="email" />
<input type="password" formControlName="password" />
<button type="submit">Registrarme</button>
</form>
Vemos que tiene algunos atributos interesantes. El primero es formGroup
. Este atributo para decir que: "este formulario será controlado por el elemento suForm
dentro del controlador.
El siguiente atributo es formControlName
, el cual lo usamos para decir que este control será asociado con el campo que definamos en el controlador.
Ahora veamos el controlador:
@Component({
selector: 'app-myform',
templateUrl: './myform.component.html',
styleUrl: './myform.component.scss'
})
export class MyFormComponent implements OnInit {
myForm: FormGroup
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.myForm = this.fb.group({
email: new FormControl(''),
password: new FormControl('')
})
}
}
Analicemos un poco esto. Si te fijas, cada elemento que definimos dentro de this.fb.group({ ... })
tiene el mismo nombre que el valor que pasamos en el atributo formControlName
de los inputs del template. Esto es porque estamos asociando el elemento HTML con el objeto FormControl que estamos creando, de esta manera, podemos establecer y obtener los valores en el input por medio de este objeto.
Manejando valores
Por medio de un FormControl
podemos tanto obtener como establecer valores de un control en el HTML de una manera programática y reactiva. Veamos un ejemplo de ello.
Para obtener el valor de un control nos basta con obtener el control y hacer uso de la propiedad value
:
const email = this.myForm.get('email').value
Y para establecer datos, usamos el método setValue
:
this.myForm.get('email').setValue('ejemplo@hola.com')
Validación de campos
Uno de los puntos más importantes en la construcción de formularios es la validación. Validar correctamente nuestros campos nos protege de entradas maliciosas y nos provee también una mejor experiencia de usuario. La validación de campos reactivos es simple. Volvamos atrás, hacia la definición de nuestros campos.
this.myForm = this.fb.group({
email: new FormControl(''),
password: new FormControl('')
})
Es aquí en donde vamos a poner nuestras validaciones. Por defecto, Angular nos provee de validaciones para la gran mayoría de casos. Puedes ver la lista de validaciones aquí. Veamos cómo los podemos agregar:
this.myForm = this.fb.group({
email: new FormControl('', [
// validaciones síncronas
Validators.required,
Validators.email
], [
// validaciones asíncronas
]),
password: new FormControl('')
})
Como podemos observar, para agregar validaciones a un control basta con pasarle al constructor de FormControl
un segundo parámetro, el cual es un arreglo de funciones validadoras. Es aquí en donde debemos agregar nuestras validaciones.
Sin embargo, existen otro tipo de validaciones, llamadas Validaciones asíncronas, la cuales, como su nombre indica, pueden retornar tanto una Promesa como como un Observable.
Validaciones asíncronas y personalizadas
Este tipo de validaciones pueden retornar solo una estructura asíncrona, como por ejemplo una Promesa o un Observable. Veamos como luce una validación personalizada y asíncrona:
validateEmailNotTaken(ctrl: AbstractControl) {
return (
this
.checkForExists(ctrl.value)
.pipe(map(taken => taken ? { taken: true } : null))
)
}
Como podemos ver es bastante sencilla. En este ejemplo, validamos que el email que se ingresa no está actualmente usado por alguna otra persona. Esto lo hacemos por medio del método checkForExists
, el cual usa el HttpClient
para retornar un Observable con un booleano para saber si existe o no. Si existe, retornamos un objeto con la propiedad taken
a la cual podemos acceder desde el template, y si no, retornamos simplemente null, indicando que no hay error.
Internamente, esta validación es resuelta por Angular, obteniendo el valor plano que engloba el observable. Simple, ¿verdad?
Propiedades útiles
Las clases FormGroup
y FormControl
tiene muchas propiedades útiles. Por ejemplo, FormGroup
tiene entre sus propiedades a valid
, el cual es un booleano que te dice si el formulario es válido, basado en si los controles han pasado las validaciones. La clase FormControl
tiene propiedades como dirty
, para saber si el control ya ha contenido valores anteriormente (luego de borrar), touched
para saber si el control ya ha sido "tocado" (luego de perder el foco) y errors
, que retorna un objeto con los errores de validación (las claves del objeto serán los nombres de las validaciones).
Veamos como podemos utilizar estas propiedades en nuestro template:
<form [formGroup]="myForm" (onSubmit)="doSomething()">
<input formControlName="email" [ngClass]="{ error: email.dirty && email.errors }" />
<span *ngIf="email.errors && email.errors.email">Ingrese un email válido</span>
<span *ngIf="email.errors && email.errors.taken">El email ya ya sido registrado</span>
<input type="password" formControlName="password" [ngClass]="{ error: password.dirty && password.errors }" />
<span *ngIf="email.errors && email.required">Ingrese su contraseña</span>
<button type="submit" [disabled]="myForm.invalid">Registrarme</button>
</form>
Excelente, nuestro formulario ahora nos indica los errores que tenemos en tiempo real, además el botón de submit se desactivará mientras el formulario contenga errores. Veamos con un poco más de detalle qué hemos hecho aquí.
En esta línea decimos: "agrega la clase 'error' al input si este ya ha tiene valores ingresados y si contiene cualquier error".
<input formControlName="email" [ngClass]="{ error: email.dirty && email.errors }" />
En esta otra línea, decimos: "muestra este span si el email ya ha sido registrado":
<span *ngIf="email.errors && email.errors.taken">El email ya ya sido registrado</span>
¡De esta manera tenemos un formulario validado y con una buena experiencia de usuario!
Conclusiones
Como sabemos, validar un formulario es muy importante, especialmente cuando nos enfrentamos a un público con skills técnicos. Así mismo, recuerda que la validación debe hacerse tanto en el lado cliente, como en el servidor. En este aspecto, Angular nos ayuda en la validación del primer tipo con validaciones tanto síncronas como asíncronas, permitiéndonos formularios seguros, complejos y usables. 😉
Top comments (2)
Excelente artículo!
Hola Gus... Excelente articulo de verdad que me ayudó a aclarar mis dudas... Gracias por compartir.