Angular Reactive Forms | Fundamentos

gugadev

gugadev

Posted on January 28, 2019

Angular Reactive Forms | Fundamentos

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 { }
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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('')
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Y para establecer datos, usamos el método setValue:

this.myForm.get('email').setValue('ejemplo@hola.com')
Enter fullscreen mode Exit fullscreen mode

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('')
})
Enter fullscreen mode Exit fullscreen mode

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('')
})
Enter fullscreen mode Exit fullscreen mode

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))
  )
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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 }" />
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

¡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. 😉

💖 💪 🙅 🚩
gugadev
gugadev

Posted on January 28, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related