Angular Reactive Forms | Fundamentos
Tags: angular, javascript, typescript
Published: 2019-01-28 16:27:04

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`:

```typescript
@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.

```html
<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:

```javascript
@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`:

```javascript
const email = this.myForm.get('email').value
```

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

```javascript
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.

```javascript
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í](https://angular.io/api/forms/Validators). Veamos cómo los podemos agregar:

```javascript
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:

```javascript
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:

```html
<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".

```html
<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":

```html
<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. 😉