Formularios model-driven (formularios reactivos)
February 17, 2019 · View on GitHub
El módulo de formularios reactivos ofrecen facilidad de utilizar patrones, testeo y validaciones directamente desde el componente en lugar de hacerlo desde la plantilla.
Utilizaremos el mismo formulario de partida de antes:
<div>
<h1>User Form</h1>
<form>
<div>
<label for="name">Name: </label>
<input type="text" id="name" name="name" required>
</div>
<div>
<label for="username">Username: </label>
<input type="text" id="username" name="username" required>
</div>
<div>
<label for="email">Email: </label>
<input type="email" id="email" name="email">
</div>
<h4>Address</h4>
<div>
<div>
<label for="street">Street:
<input type="text" id="street" name="street">
</label>
</div>
<div>
<label for="city">City:
<input type="text" id="city" name="city">
</label>
</div>
<div>
<label for="zipcode">Zip Code:
<input type="text" id="zipcode" name="zipcode">
</label>
</div>
</div>
<button type="submit">Submit</button>
</form>
</div>
El mismo componete de partida,
import { Component } from '@angular/core';
import { USERS } from 'app/data/users';
import { User } from 'app/model/TypicodeUser';
@Component({
selector: 'hero-form',
templateUrl: './hero-form.component.html'
})
export class ReactiveFormComponent {
user: User = USERS[0];
}
Y esta vez importamos el módulo ReactiveFormsModule de @angular/forms.
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
...
ReactiveFormsModule,
...
],
Al contrario de lo que ocurría con los formularios template-driven, en esta ocasión prácticamente toda la programación estará en el componente.
Clases esenciales
Conviene echar un vistazo a las clases que nos proporciona ReactiveFormsModule
- AbstractControl: Es la clase base abstracta de las otras 3 clases: FormControl, FormGroup, y FormArray.
- FormControl: Controla el valor y estado de validez de un control de formulario individual. Se corresponede con un control de formulario HTML como un input o un select.
- FormGroup: Controla el valor y validez de un grupo de instancias AbstractControl. Todo formulario es un único FormGroup. Ya dentro de ese FormGroup puede haber cualquier cantidad de FormControl, FormGroup o FormArray
- FormArray: Controla el valor y estado de validez de un array de instancias AbstractControl.
FormGroup básico
export class ReactiveFormComponent {
userForm: FormGroup;
constructor(private fb: FormBuilder) {
this.createForm();
}
createForm() {
this.userForm = this.fb.group({
name: ['', [Validators.required] ],
username: ['', [Validators.required] ],
email: ['', [Validators.email] ],
street: '',
city: '',
zipcode: ''
});
}
}
En este ejemplo, el formulario heroForm consta de un FormGroup con 7 FormControls.
Prácticamente lo único que tendremos que hacer en el html es asociar el form con el FormGroup heroForm
<form [formGroup]="userForm">
y cada control HTML con su control correspondiente FormControl.
<input class="form-control" formControlName="name">
Anindando FormGroup
createForm() {
this.userForm = this.fb.group({
name: ['', Validators.required ],
username: ['', [Validators.required] ],
email: ['', [Validators.email] ],
address: this.fb.group({
street: '',
city: '',
zipcode: ''
})
});
Y en el html, una pequeña modificación
<h4>Address</h4>
<div formGroupName="address">
## AbstractControl
Llegados hasta aquí, merece la pena echar un vistazo a las propiedades y los métodos de la clase AbstractControl: https://angular.io/api/forms/AbstractControl
FormControl
this.usernameControl = new FormControl('Valor por defecto', Validators.required);
this.userForm = this.fb.group({
name: 'Nombre por defecto',
username: this.usernameControl
});
- setValue()
- patchValue()
- reset()
- registerOnChange()
- registerOnDisabledChange()
FormGroup
Con el método get de FormGroup (en realidad, de AbstractControl) podemos acceder a un control concreto
const nameControl = this.userForm.get('name');
O en el html
<p>Name value: {{ userForm.get('name').value }}</p>
<p>Street value: {{ userForm.get('address.street').value}}</p>
Métodos y propiedades específicos de FormGroup:
- controls
- addControl(name: string, control: AbstractControl): void
- removeControl(name: string): void
- setControl(name: string, control: AbstractControl): void
- contains(controlName: string): boolean
- setValue(value: { [key: string]: any; }, options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
- patchValue(value: { [key: string]: any; }, options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
- reset(value: any = {}, options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
- getRawValue(): any
FormGroup.setValue()
Podemos establecer valores en cualquier momento al formulario completo con setValue. Se pueden asociar los valores de un objeto completo si las propiedades del objeto y del formulario coinciden.
this.heroForm.setValue({
name: this.hero.name,
address: this.hero.addresses[0] || new Address()
});
FormGroup.reset()
También podemos resetear el formulario
this.heroForm.reset();
O un control
control.reset('Drew');
control.reset({ value: 'Drew', disabled: true });
FormControl.setValidators()
También tenemos una función para establecer validadores dinámicamente:
this.nameController.setValidators([Validators.required, Validators.minLength(5)]);
this.nameController.setValidators(null);
FormArray
this.userForm = this.fb.group({
name: ['', Validators.required ],
addresses: this.fb.array([]),
power: '',
sidekick: ''
});
El array se podrá recorrer mediante la propiedad .controls
<div formArrayName="addresses">
<div *ngFor="let address of addresses.controls; let i=index" [formGroupName]="i" >
<!-- The repeated address template -->
</div>
</div>
Y se pueden añadir utilizando el método push como en un array normal y corriente.
<button (click)="addAddress()" type="button">Add address</button>
addAddress() {
this.userForm.get('addresses').push(this.fb.group(new Address()));
}
Métodos y propiedades específicos de FormArray
- controls: AbstractControl[]
- length: number
- at(index: number): AbstractControl
- push(control: AbstractControl): void
- insert(index: number, control: AbstractControl): void
- removeAt(index: number): void
- setControl(index: number, control: AbstractControl): void
- setValue(value: any[], options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
- patchValue(value: any[], options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
- reset(value: any = [], options: { onlySelf?: boolean; emitEvent?: boolean; } = {}): void
- getRawValue(): any[]
Validadores
https://angular.io/api/forms/Validators
Observar cambios en un control
Los formularios reactivos tienen algunas características que funcionan con Observables. Por ejemplo la clase FormControl tiene un método valueChanges que devuelve un Observable cada vez que el valor cambia.
nameChangeLog: string[] = [];
constructor(private fb: FormBuilder) {
this.createForm();
this.logNameChange();
}
logNameChange() {
const nameControl = this.userForm.get('name');
nameControl.valueChanges.subscribe(
(value: string) => this.nameChangeLog.push(value)
);
}
updateOn
El valor por defecto de updateOn es 'change' pero puede tomar uno de estos 3 valores:
- 'change'
- 'blur'
- 'submit'
En un control individual
const control = new FormControl('Initial Value', { validators: Validators.required, updateOn: 'blur' });
O en un grupo entero o en un array
this.formControl = new FormGroup({
name: ['', { validators: Validators.required }],
addresses: this.fb.array([]),
}, { updateOn: 'blur' });
this.formControl = new FormGroup({
name: ['', { validators: Validators.required }],
addresses: this.fb.array([]),
}, { updateOn: 'submit' });
const arr = new FormArray([
new FormControl('Nancy'),
new FormControl('Drew')
], {validators: myValidator, asyncValidators: myAsyncValidator, updateOn: 'blur'});