# Formularios

## Tipos de Formularios

#### Template Forms

* Son formularios controlados por modelo
* El modelo basado en plantillas
* Angular crea modelos como **FormGroups** y **FormControls**
* Directivas de Angular **NgForm** y **NgModel**

#### Reactive Forms

Mucho mas potentes y mejor rendimiento

* Basados en **Observables**
* Mejora el tipo de validación de los datos
* Se usa para Formularios complejos

## FormControl

Es la pieza mas atómica de un **formulario reactivo.**

Para poder utilizarlo se necesita importar el módulo `ReactiveFormsModule`, en el modulo principal

```typescript
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  declarations: [ ... ],
  imports: [ 
    ...
    ReactiveFormsModule,
    ...
  ],
  providers: [ ... ],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

Para usarlo dentro de un componente

<pre class="language-typescript"><code class="lang-typescript"><strong>export class BasicFormComponent implements OnInit {
</strong>  // Crear un form control
  nameField = new FormControl('soy un control');

  ngOnInit() {
    // Optiene valores desde la vista por medio de una suscripcion
    this.nameField.valueChanges.subscribe((name) => {
      console.log(`Name changed to: ${name}`);
    });
  }
}
</code></pre>

```html
// Enlazar el form cotrol con el template
<input type="text" [formControl]="nameField" />

// Obtener el valor
{{ nameField.value }}

// Obtener el objeto value
<code>
  <pre> {{ nameField | json }} </pre>
</code>
```

## Manejo y binding de selects y selects múltiples

```typescript
export class BasicFormComponent {
  categoryField = new FormControl('category-3');

  array = [
    { key: 'category-1', 'name': 'Category 1' },
    { key: 'category-2', 'name': 'Category 2' },
    { key: 'category-3', 'name': 'Category 3' },
    { key: 'category-4', 'name': 'Category 4' },
    { key: 'category-5', 'name': 'Category 5' },
    { key: 'category-6', 'name': 'Category 6' },
    { key: 'category-7', 'name': 'Category 7' },
    { key: 'category-8', 'name': 'Category 8' },
    { key: 'category-9', 'name': 'Category 9' },
  ];

  getNameValue() {
    console.log(this.categoryField.value);
  }
}
```

```markup
<select id="category" [formControl]="categoryField">
  <option *ngFor="let item of array" [value]="item.key">{{item.name}}</option>
</select>
<!-- o con ngVAlue para objectos -->
<select id="category" [formControl]="categoryField">
  <option *ngFor="let item of array" [ngValue]="item">{{item.name}}</option>
</select>

<!-- También se puede enviar todo el objeto y en el componente usar JSON.parse para convertirlo-->
<!-- <option *ngFor="let item of array" [value]="item | json">{{item.name}}</option> -->

```

Para setear por defecto el valor del select que utiliza *ngValue*, se puede usar por ejemplo:

```typescript
this.form.controls['<formName>'].setValue( {...}, {onlySelf: true});
```

Con múltiple

```markup
<label for="category">Category</label>
<select id="category" [formControl]="categoryField" multiple>
  <option *ngFor="let item of array" [value]="item.name">{{item.name}}</option>
</select>
```

Y observable

```typescript
...
  ngOnInit() {
    this.categoryField.valueChanges.subscribe((nameArr: any) => {
      console.log(nameArr);
    });
  }
...
```

## Manejo y binding de inputs radio y checkbox

```typescript
genderField = new FormControl('');
zoneField = new FormControl('');
```

```html
<p>
  Agree: {{ agreeField.value }}
  <input type="checkbox" [formControl]="agreeField" />
</p>
<p>
  Gender: {{ genderField.value }}
  <label>
    <input
      name="gender"
      value="male"
      type="radio"
      [formControl]="genderField"
    />
    Male
  </label>
  <label>
    <input
      name="gender"
      value="female"
      type="radio"
      [formControl]="genderField"
    />
    Female
  </label>
  <label>
    <input
      name="gender"
      value="other"
      type="radio"
      [formControl]="genderField"
    />
    Other
  </label>
</p>
```

## Aplica validaciones a un FormControl

La clase FormControl recibe aparte del valor, puede recibir una o varias validaciones sincronas y asíncronas.

```typescript
new FormControl('', [Validators.required]);
```

```markup
<p>
  Name: {{ nameField.value }}
  <input type="text" [formControl]="nameField" />
  <button [disabled]="nameField.hasError('required')" (click)="getNameValue()">
    Get Value
  </button>
  <!-- ó -->
  <button [disabled]="nameField.invalid" (click)="getNameValue()">
    Get Value
  </button>
</p>
```

una mala préctica es mostrar el error desde el antes de utilizar el `input` para esto podemos usar la propiedad `touched`.

<https://angular.io/api/forms/Validators>

## FormGroup

Un *FormGroup* es un grupo de *FormContols* y sirve para manejar todo el formulario, al igual que el *FormControl* tiene validaciones el *FormGroup* también pero a nivel de  todo el form.&#x20;

```typescript
...
  form = new FormGroup({
    name: new FormControl('', [Validators.required, Validators.maxLength(10)]),
  });

  get nameField() {
    return this.form.get('name');
  }
  
  save(e: MouseEvent) {
    e.preventDefault()
    ...
  }
...
```

```html
<form [formGroup]="form" (ngSubmit)="save($event)">
      <p>
      Name: {{ nameField.value }} {{ nameField.valid }}
      <input
            [class.is-valid]="isNameFieldValid"
            [class.is-invalid]="isNameFieldInvalid"
            type="text"
            formControlName="name"/>
      <button [disabled]="nameField.invalid" type="submit">Get value</button>
      </p>
</form>
```

## **FormBuilder**

Con **FormBuilder** se tiene una sintaxis un poco mas amigable

```typescript
...
  form: FormGroup;

  constructor(
    private formBuilder: FormBuilder
  ) {
    this.buildForm();
  }

  private buildForm() {
    this.form = this.formBuilder.group({
      name: ['', [Validators.required, Validators.maxLength(10)]],
      ...
    });
  }

  get nameField() {
    return this.form.get('name');
  }
  
  save(e: MouseEvent) {
    e.preventDefault()
    if(this.form.valid) {
      ...
    } else {
      this.form.markAllAsTouched();
    }
  }
...
```

## Multiples FormGroups

Sirve para agrupar campos de formulario con su validaciones independientes.

```markup
<form [formGroup]="form" (ngSubmit)="save($event)">
  <p>
    Name: 
    <input [disabled]="true" type="text" formControlName="name" />  
  </p>
  Address:
  <div formGroupName="addresses">
    <input  type="text" formControlName="address1" />
    <input  type="text" formControlName="address2" />
    <span> {{addresses.valid}}</span>
  </div>
  <button type="submit" [disabled]="form.invalid">
      Send
  </button>
</form>
```

```typescript
  ...
  form!: FormGroup;

  constructor(private formBuilder: FormBuilder) {
    this.buildForm();
  }

  private buildForm() {
    this.form = this.formBuilder.group({
      name: ['', [Validators.required, Validators.maxLength(10)]],
      addresses: this.formBuilder.group({
        address1: ['', [Validators.required]],
        address2: ['', [Validators.required]],
      }),
    });
  }

  get nameField() {
    return this.form.get('name')!;
  }

  get addresses() {
    return this.form.get('addresses')!;
  }

  get address1() {
    return this.form.get('addresses')!.get('address1')!;
  }

  get address2() {
    return this.form.get('addresses')!.get('address1')!;
  }

  save(e: MouseEvent) {
    e.preventDefault()
    console.log(this.nameField.value);
  }
...
```

## Validaciones personalizadas

```typescript
import { AbstractControl } from '@angular/forms';

export class MyValidators {

  static validPassword(control: AbstractControl) {
    const value = control.value;
    if (!containsNumber(value)) {
      return {invalid_password: true};
    }
    return null;
  }

  static matchPasswords(control: AbstractControl) {
    const password = control.get('password').value;
    const confirmPassword = control.get('confirmPassword').value;
    if (password !== confirmPassword) {
      return {match_password: true};
    }
    return null;
  }

}

function containsNumber(value: string){
  return value.split('').find(v => isNumber(v)) !== undefined;
}


function isNumber(value: string){
  return !isNaN(parseInt(value, 10));
}
```

```typescript
private buildForm() {
    this.form = this.formBuilder.group({
      email: ['', [Validators.required]],
      password: ['', [Validators.required, Validators.minLength(6), MyValidators.validPassword]],
      confirmPassword: ['', [Validators.required]],
    }, {
      validators: MyValidators.matchPasswords // Validación grupal
    });
  }
```

```html
<div>
    <input placeholder="password" formControlName="confirmPassword" type="password">
    <div *ngIf="form.get('confirmPassword').touched && form.errors">
    <mat-error *ngIf="form.hasError('match_password')">
    No hace match
    </mat-error>
</div>
```

Las validaciones grupales dependen de uno o mas campos del mismo formulario y lo validan todo.

## Validaciones asincrónicas

Un ejemplo sería al intentar crear una categoría duplicada

```typescript
import { AbstractControl } from '@angular/forms';
import { map } from 'rxjs/operators';
import { CategoriesService } from './../core/services/categories.service';

export class MyValidators {
  ...
  static validateCategory(service: CategoriesService) {
    return (control: AbstractControl) => {
      const value = control.value;
      return service.checkCategory(value)
      .pipe(
        map((response: any) => {
          const isAvailable = response.isAvailable;
          if (!isAvailable) {
            return {not_available: true};
          }
          return null;
        })
      );
    };
  }
  ...
}
```

```typescript
private buildForm() {
    this.form = this.formBuilder.group({
      name: ['', [Validators.required, Validators.minLength(4)], MyValidators.validateCategory(this.categoriesService)],
      image: ['', Validators.required]
    });
  }
```

```html
<div *ngIf="nameField.hasError('not_available')">
    Este nombre ya lo tiene otra categoría
</div>
```

## PatchValue

Sirve para *setear* todos los datos de un formulario en una sola sentencia, los datos deben de estar en formato *JSON*

```typescript
this.form.patchValue(data)
```

## Agregar campos en tiempo de ejecución

```typescript
...
  private buildForm() {
    this.form = this.formBuilder.group({
      address: this.formBuilder.array([]),
    });
  }

  get addressField() {
    return this.form.get('address') as FormArray;
  }
  
  addAddressField() {
    this.addressField.push(createAddressField())
  }
  
  private createAddressField() {
    return this.form.get('address') as FormArray;
  }
...
```

```markup
<div>
    <button type="button" (click)="addAddressField()">Add address</button>
    <div formArrayName="address" *ngFor="let address of addressField.controls; let i=index">
    <div [formGroupName]="i">
        <label>ZipCode</label>
        <input placeholder="zip" formControlName="zip" type="text">  
        <label>Text</mat-label>
        <input placeholder="text" formControlName="text" type="text">
    </div>
</div>
```

Un *`formBuilder.array`* puede contener *`formBuilder.group`* o new `FormControl.`

#### **Uso de `ViewContainerRef` y Componentes Dinámicos en Angular**

**Servicio para manejar el modal**

```typescript
import { Injectable, ViewContainerRef, ComponentRef, Type } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class ModalService {
  private viewContainerRef!: ViewContainerRef;
  private modalRef: ComponentRef<any> | null = null;

  registerContainer(viewContainerRef: ViewContainerRef) {
    this.viewContainerRef = viewContainerRef;
  }

  openModal<T>(component: Type<T>, data: any = {}) {
    if (this.modalRef) return; // Evitar múltiples modales
    this.modalRef = this.viewContainerRef.createComponent(component);
    Object.assign(this.modalRef.instance, data);
  }

  closeModal() {
    if (this.modalRef) {
      this.modalRef.destroy();
      this.modalRef = null;
    }
  }
}
```

***

**Contenedor del modal**

```typescript
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
import { ModalService } from './modal.service';

@Component({
  selector: 'app-modal-anchor',
  template: '<ng-container #modalContainer></ng-container>',
})
export class ModalAnchorComponent {
  @ViewChild('modalContainer', { read: ViewContainerRef, static: true })
  modalContainer!: ViewContainerRef;

  constructor(private modalService: ModalService) {}

  ngAfterViewInit() {
    this.modalService.registerContainer(this.modalContainer);
  }
}
```

***

**Componente modal dinámico**

```typescript
import { Component } from '@angular/core';

@Component({
  selector: 'app-dynamic-modal',
  template: `
    <div class="modal">
      <h2>{{ title }}</h2>
      <p>{{ message }}</p>
      <button (click)="close()">Close</button>
    </div>
  `,
})
export class DynamicModalComponent {
  title!: string;
  message!: string;

  close() {
    console.log('Modal closed!');
  }
}
```

***

**Usando el modal dinámico**

```typescript
import { Component } from '@angular/core';
import { ModalService } from './modal.service';
import { DynamicModalComponent } from './dynamic-modal.component';

@Component({
  selector: 'app-root',
  template: `
    <button (click)="openModal()">Open Modal</button>
    <app-modal-anchor></app-modal-anchor>
  `,
})
export class AppComponent {
  constructor(private modalService: ModalService) {}

  openModal() {
    this.modalService.openModal(DynamicModalComponent, {
      title: 'Dynamic Modal',
      message: 'Hello from Angular 9+!',
    });
  }
}
```

***
