Los formularios reactivos proporcionan un enfoque basado en un modelo para manejar las entradas de un formulario cuyos valores cambian con el tiempo. Esta guía te muestra cómo crear y actualizar un control de formulario básico, usar múltiples controles en un grupo, validar los valores de un formulario y crear formularios dinámicos en los que puedes agregar o eliminar controles en tiempo de ejecución.
Visión general de los formularios reactivos
Los formularios reactivos usan un enfoque explícito e inmutable para gestionar el estado de un formulario en cualquier momento. Cada cambio en el estado del formulario devuelve un nuevo estado, lo que mantiene la integridad del modelo entre cambios. Los formularios reactivos se construyen alrededor de flujos observables, donde las entradas y los valores del formulario se proporcionan como flujos de valores de entrada, a los que se puede acceder de forma síncrona.
Los formularios reactivos también proporcionan un camino directo hacia las pruebas porque tienes la seguridad de que tus datos son consistentes y predecibles cuando se solicitan. Cualquier consumidor de los flujos tiene puede manipular esos datos de forma segura.
Los formularios reactivos difieren de los formularios basados en plantillas de maneras distintas. Los formularios reactivos proporcionan acceso síncrono al modelo de datos, inmutabilidad con operadores observables y seguimiento de cambios a través de flujos observables.
Los formularios basados en plantillas permiten acceso directo para modificar datos en tu plantilla, pero son menos explícitos que los formularios reactivos porque dependen de directivas incrustadas en la plantilla, junto con datos mutables para rastrear cambios de forma asíncrona. Consulta la Visión general de Formularios para comparaciones detalladas entre los dos paradigmas.
Añadiendo un control de formulario básico
Para usar controles de formulario, hay tres pasos.
- Generar un nuevo componente y registrar el módulo de formularios reactivos. Este módulo declara las directivas de formularios reactivos que necesitas para usar formularios reactivos.
- Instanciar un
FormControl
. - Registrar el
FormControl
en la plantilla.
Luego puedes mostrar el formulario agregando el componente a la plantilla.
Los siguientes ejemplos muestran cómo agregar un único control de formulario. En el ejemplo, el usuario ingresa su nombre en un campo de entrada, captura ese valor de entrada y muestra el valor actual del control del formulario.
-
Generar un nuevo componente e importar ReactiveFormsModule
Usa el comando CLI
ng generate component
para generar un componente en tu proyecto e importaReactiveFormsModule
del paquete@angular/forms
y agrégalo al array deimports
de tu Componente.src/app/name-editor/name-editor.component.ts (excerpt)
import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}
-
Declarar una instancia de FormControl
Usa el constructor de
FormControl
para establecer su valor inicial, que en este caso es una cadena vacía. Al crear estos controles en tu clase de componente, obtienes acceso inmediato para escuchar, actualizar y validar el estado de la entrada del formulario.src/app/name-editor/name-editor.component.ts
import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}
-
Registrar el control en la plantilla
Después de crear el control en la clase del componente, debes asociarlo con un control de formulario en la plantilla. Actualiza la plantilla con el control de formulario usando el enlace
formControl
proporcionado porFormControlDirective
, que también está incluido enReactiveFormsModule
.src/app/name-editor/name-editor.component.html
<label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button>
Usando la sintaxis de enlace de plantilla, el control de formulario ahora está registrado en el elemento de entrada
name
de la plantilla. El control de formulario y el elemento DOM se comunican entre sí: la vista refleja cambios en el modelo, y el modelo refleja los cambios en la vista. -
Mostrar el componente
El
FormControl
asignado a la propiedadname
se muestra cuando el componente<app-name-editor>
se agrega a una plantilla.src/app/app.component.html (editor de nombre)
<h1>Reactive Forms</h1><app-name-editor /><app-profile-editor />
Mostrando un valor de control de formulario
Puedes mostrar el valor de las siguientes maneras:
- A través del observable
valueChanges
, donde puedes escuchar cambios en el valor del formulario en la plantilla usandoAsyncPipe
o en la clase del componente usando el métodosubscribe()
- Con la propiedad
value
, que te da proporciona el valor actual en ese momento.
El siguiente ejemplo te muestra cómo mostrar el valor actual usando interpolación en la plantilla.
src/app/name-editor/name-editor.component.html (control value)
<label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button>
El valor mostrado cambia a medida que actualizas el control del formulario.
Los formularios reactivos proporcionan acceso a información sobre un control dado a través de propiedades y métodos proporcionados con cada instancia. Estas propiedades y métodos de la clase subyacente AbstractControl se usan para controlar el estado del formulario y determinar cuándo mostrar mensajes al manejar validación de entrada.
Lee sobre otras propiedades y métodos de FormControl
en la Referencia de API.
Reemplazando un valor de control de formulario
Los formularios reactivos tienen métodos para cambiar el valor de un control de forma programática, lo que te da la flexibilidad de actualizar el valor sin necesidad de la interacción del usuario.
Una instancia de FormControl
proporciona un método setValue()
que actualiza el valor del control de formulario y valida la estructura del valor proporcionado contra la estructura del control.
Por ejemplo, cuando recuperas datos del formulario desde una API o servicio backend, usa el método setValue()
para actualizar el control a su nuevo valor, reemplazando completamente el valor anterior.
El siguiente ejemplo agrega un método a la clase del componente para actualizar el valor del control a Nancy usando el método setValue()
.
src/app/name-editor/name-editor.component.ts (update value)
import {Component} from '@angular/core';import {FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'], imports: [ReactiveFormsModule],})export class NameEditorComponent { name = new FormControl(''); updateName() { this.name.setValue('Nancy'); }}
Actualiza la plantilla con un botón para simular una actualización de nombre. Cuando haces clic en el botón Actualizar Nombre, el valor ingresado en el control del formulario se refleja como su valor actual.
src/app/name-editor/name-editor.component.html (update value)
<label for="name">Name: </label><input id="name" type="text" [formControl]="name"><p>Value: {{ name.value }}</p><button type="button" (click)="updateName()">Update Name</button>
El modelo del formulario es la fuente de la verdad para el control. Por lo tanto, cuando haces clic en el botón, el valor de la entrada cambia dentro de la clase del componente, sobrescribiendo su valor actual.
ÚTIL: En este ejemplo, estás usando un control único.
Cuando uses el método setValue()
con una instancia de grupo de formulario o array de formulario, el valor necesita coincidir con la estructura del grupo o array.
Agrupando controles de formulario
Los formularios típicamente contienen varios controles relacionados. Los formularios reactivos proporcionan dos formas de agrupar múltiples controles relacionados en un formulario de entrada único.
Grupos de formulario | Detalle |
---|---|
Form group | Define un formulario con un conjunto fijo de controles que puedes gestionar juntos. Los conceptos básicos del grupo de formulario se discuten en esta sección. También puedes anidar grupos de formulario para crear formularios más complejos. |
Form array | Define un formulario dinámico, en los que puedes agregar y eliminar controles en tiempo de ejecución. También puedes anidar arrays de formulario para crear formularios más complejos. Para más sobre esta opción, consulta Crear formularios dinámicos. |
Al igual que una instancia de FormControl
(control de formulario) te da control sobre un solo campo de entrada, una instancia de FormGroup
(grupo de formulario) rastrea el estado del formulario de un grupo de instancias de FormControl
(por ejemplo, un formulario).
Cada control en una instancia de FormGroup
se rastrea por nombre al crear el grupo.
El siguiente ejemplo muestra cómo gestionar múltiples instancias de FormControl
en un solo grupo.
Genera un componente ProfileEditor
e importa las clases FormGroup
y FormControl
del paquete @angular/forms
.
ng generate component ProfileEditor
src/app/profile-editor/profile-editor.component.ts (imports)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
Para agregar un grupo de formulario a este componente, sigue los siguientes pasos.
- Crear una instancia de
FormGroup
. - Asociar el modelo y vista de
FormGroup
. - Guardar los datos del formulario.
-
Crear una instancia de FormGroup
Crea una propiedad en la clase del componente llamada
profileForm
y establece la propiedad a una nueva instancia deFormGroup
. Para inicializar elFormGroup
, proporciona al constructor un objeto de claves nombradas mapeadas a su control.Para el formulario de perfil, agrega dos instancias de
FormControl
con los nombresfirstName
ylastName
src/app/profile-editor/profile-editor.component.ts (grupo de formulario)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
Los controles de formulario individuales ahora están recopilados dentro de un grupo. Una instancia de
FormGroup
proporciona su valor de modelo como un objeto reducido de los valores de cada control en el grupo. Una instancia deFormGroup
tiene las mismas propiedades (comovalue
yuntouched
) y métodos (comosetValue()
) que una instancia de control de formulario. -
Asociar el modelo y vista de FormGroup
Un grupo de formulario rastrea el estado y los cambios para cada uno de sus controles, por lo que si uno de los controles cambia, el control padre también emite un nuevo estado o cambio de valor. El modelo para el grupo se mantiene a partir de sus miembros. Después de definir el modelo, debes actualizar la plantilla para reflejar el modelo en la vista.
src/app/profile-editor/profile-editor.component.html (template form group)
<form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
Así como un grupo de formulario contiene un grupo de controles, el
FormGroup
profileForm está vinculado al elementoform
con la directivaFormGroup
, creando una capa de comunicación entre el modelo y el formulario que contiene las entradas. La entradaformControlName
proporcionada por la directivaFormControlName
vincula cada entrada individual al control de formulario definido enFormGroup
. Los controles de formulario se comunican con sus respectivos elementos. También comunican cambios a la instancia deFormGroup
, que proporciona la fuente de verdad para el valor del modelo. -
Guardar datos del formulario
El componente
ProfileEditor
acepta entrada del usuario, pero en un escenario real quieres capturar el valor del formulario y hacerlo disponible para procesamiento adicional fuera del componente. La directivaFormGroup
escucha el eventosubmit
emitido por el elementoform
y emite un eventongSubmit
que puedes vincular a una función de callback. Agrega un listener de eventongSubmit
a la etiquetaform
con el método de callbackonSubmit()
.src/app/profile-editor/profile-editor.component.html (submit event)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
El método
onSubmit()
en el componenteProfileEditor
captura el valor actual deprofileForm
. UsaEventEmitter
para mantener el formulario encapsulado y proporcionar el valor del formulario fuera del componente. El siguiente ejemplo usaconsole.warn
para registrar un mensaje en la consola del navegador.src/app/profile-editor/profile-editor.component.ts (submit method)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
El evento
submit
es emitido por la etiquetaform
usando el evento DOM integrado. Activas el evento haciendo clic en un botón con tiposubmit
. Esto permite que el usuario presionar la tecla Enter para enviar el formulario completado.Usa un elemento
button
para agregar un botón al final del formulario para activar el envío del formulario.src/app/profile-editor/profile-editor.component.html (submit button)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
El botón en el fragmento anterior también tiene un enlace
disabled
adjunto para deshabilitar el botón cuandoprofileForm
es inválido. Aún no estás realizando ninguna validación, así que el botón siempre está habilitado. La validación básica de formularios se cubre en la sección Validar entrada de formulario. -
Mostrar el componente
Para mostrar el componente
ProfileEditor
que contiene el formulario, agrégalo a una plantilla de componente.src/app/app.component.html (profile editor)
<h1>Reactive Forms</h1><app-name-editor /><app-profile-editor />
ProfileEditor
te permite gestionar las instancias deFormControl
para los controlesfirstName
ylastName
dentro de la instancia deFormGroup
.Creando grupos de formulario anidados
Los
FormGroup
pueden aceptar tanto instancias deFormControl
individuales como otras instancias deFormGroup
como hijos. Esto hace que la composición de modelos de formulario complejos sea más fácil de mantener y agruparlos lógicamente.Al construir formularios complejos, gestionar las diferentes áreas de información es más fácil en secciones más pequeñas. Usar una instancia de
FormGroup
anidado te permite dividir grandes grupos de formularios en otros más pequeños y manejables.Para hacer formularios más complejos, sigue estos pasos.
- Crear un grupo anidado.
- Agrupar el formulario anidado en la plantilla.
Algunos tipos de información naturalmente caen en el mismo grupo. Un nombre y dirección son ejemplos típicos de tales grupos anidados, y se usan en los siguientes ejemplos.
Para crear un grupo anidado en `profileForm`, agrega un elemento anidado `address` a la instancia de `FormGroup`. src/app/profile-editor/profile-editor.component.ts (nested form group)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
En este ejemplo,
address group
combina los controles actualesfirstName
ylastName
con los nuevos controlesstreet
,city
,state
, yzip
. Aunque el elementoaddress
en el grupo de formulario es un hijo del elemento generalprofileForm
en el grupo de formulario, las mismas reglas se aplican con cambios de valor y estado. Los cambios en estado y valor del grupo de formulario anidado se propagan al grupo de formulario padre, manteniendo consistencia con el modelo general. -
Agrupar el formulario anidado en la plantilla
Después de actualizar el modelo en la clase del componente, actualiza la plantilla para conectar la instancia de
FormGroup
y sus elementos de entrada. Agrega el grupo de formularioaddress
que contiene los camposstreet
,city
,state
, yzip
a la plantillaProfileEditor
.src/app/profile-editor/profile-editor.component.html (template nested form group)
<form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
El formulario
ProfileEditor
se muestra como un grupo, pero el modelo se descompone más para representar las áreas de agrupación lógica.Muestra el valor para la instancia de
FormGroup
en la plantilla del componente usando la propiedadvalue
yJsonPipe
.
Actualizando partes del modelo de datos
Cuando actualizas el valor de una instancia de FormGroup
que contiene múltiples controles, es posible que solo desees actualizar partes del modelo.
Esta sección cubre cómo actualizar partes específicas del modelo de datos de un control de formulario.
Hay dos formas de actualizar el valor del modelo:
Métodos | Detalles |
---|---|
setValue() |
Establece un nuevo valor para un control individual. El método setValue() se adhiere estrictamente a la estructura del grupo de formulario y reemplaza el valor completo del control. |
patchValue() |
Reemplaza cualquier propiedad definida en el objeto que haya cambiado en el modelo del formulario. |
Las verificaciones estrictas del método setValue()
ayudan a detectar errores de anidación en formularios complejos, mientras que patchValue()
no muestra un error en estos casos.
En ProfileEditorComponent
, usa el método updateProfile
con el siguiente ejemplo para actualizar el nombre y la dirección de la calle para el usuario.
src/app/profile-editor/profile-editor.component.ts (patch value)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
Simula una actualización agregando un botón a la plantilla para actualizar el perfil del usuario bajo demanda.
src/app/profile-editor/profile-editor.component.html (update value)
<form [formGroup]="profileForm"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for(alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias: </label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div></form><p>Form Value: {{ profileForm.value | json }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
Cuando un usuario hace clic en el botón, el modelo profileForm
se actualiza con nuevos valores para firstName
y street
. Observa que street
se proporciona en un objeto dentro de la propiedad address
.
Esto es necesario porque el método patchValue()
aplica la actualización contra la estructura del modelo.
PatchValue()
solo actualiza propiedades que el modelo del formulario define.
Empleando el servicio FormBuilder para generar controles
Crear instancias de FormControl
manualmente puede volverse repetitivo cuando se trabaja con múltiples formularios.
El servicio FormBuilder
proporciona métodos convenientes para generar controles.
Usa los siguientes pasos para aprovechar este servicio.
- Importar la clase
FormBuilder
. - Inyectar el servicio
FormBuilder
. - Generar el contenido del formulario.
Los siguientes ejemplos muestran cómo refactorizar el componente ProfileEditor
para usar el servicio form builder para crear instancias de FormControl
y FormGroup
.
-
Importar la clase FormBuilder
Importa la clase
FormBuilder
del paquete@angular/forms
.src/app/profile-editor/profile-editor.component.ts (import)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
-
Inyectar el servicio FormBuilder
El servicio
FormBuilder
es un proveedor inyectable del módulo de formularios reactivos. Usa la funcióninject()
para inyectar esta dependencia en tu componente.src/app/profile-editor/profile-editor.component.ts (property init)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
-
Generar controles de formulario
El servicio
FormBuilder
tiene tres métodos:control()
,group()
yarray()
. Estos son métodos de fábrica para generar instancias en tus clases de componente incluyendo controles de formulario, grupos de formulario y arrays de formulario. Usa el métodogroup
para crear los controles deprofileForm
.src/app/profile-editor/profile-editor.component.ts (form builder)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
En el ejemplo anterior, usas el método
group()
con el mismo objeto para definir las propiedades en el modelo. El valor para cada nombre de control es un array que contiene el valor inicial como el primer elemento en el array.SUGERENCIA: Puedes definir el control con solo el valor inicial, pero si tus controles necesitan validación síncrona o asíncrona, agrega validadores síncronos y asíncronos como el segundo y tercer elemento en el array. Compara usar el form builder con crear las instancias manualmente.
src/app/profile-editor/profile-editor.component.ts (instances)
import {Component} from '@angular/core';import {FormGroup, FormControl, ReactiveFormsModule} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { profileForm = new FormGroup({ firstName: new FormControl(''), lastName: new FormControl(''), address: new FormGroup({ street: new FormControl(''), city: new FormControl(''), state: new FormControl(''), zip: new FormControl(''), }), }); updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); }}
src/app/profile-editor/profile-editor.component.ts (form builder)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
Validando la entrada de formulario
La validación de formularios se usa para asegurar que la entrada del usuario sea completa y correcta. Esta sección cubre agregar un validador único a un control de formulario y cómo mostrar el estado general del formulario. La validación de formularios se cubre más extensivamente en la guía Validación de Formularios.
Usa los siguientes pasos para agregar validación de formulario.
- Importar una función validadora en tu componente de formulario.
- Agregar el validador al campo en el formulario.
- Agregar lógica para manejar el estado de validación.
La validación más común es hacer un campo requerido.
El siguiente ejemplo muestra cómo agregar una validación requerida al control firstName
y mostrar el resultado de la validación.
-
Importar una función validadora
Los formularios reactivos incluyen un conjunto de funciones validadoras para casos de uso comunes. Estas funciones reciben un control, lo validan y devuelven un objeto de error o un valor nulo según el resultado de la verificación.
Importa la clase
Validators
del paquete@angular/forms
.src/app/profile-editor/profile-editor.component.ts (import)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
-
Hacer un campo requerido
En el componente
ProfileEditor
, agrega el método estáticoValidators.required
como el segundo elemento en el array para el controlfirstName
.src/app/profile-editor/profile-editor.component.ts (required validator)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
-
Mostrar estado del formulario
Cuando agregas un campo requerido a un control del formulario, su estado inicial es inválido. Este estado inválido se propaga al elemento del grupo de formulario padre, haciendo su estado inválido. Accede al estado actual de la instancia de
FormGroup
a través de su propiedadstatus
.Muestra el estado actual de
profileForm
usando interpolación.src/app/profile-editor/profile-editor.component.html (display status)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
El botón Submit está deshabilitado porque
profileForm
es inválido debido al control de formulariofirstName
requerido. Después de completar la entradafirstName
, el formulario se vuelve válido y el botón Submit se habilita.Para más información sobre la validación de formularios, visita la guía Validación de Formularios.
Creando formularios dinámicos
FormArray
es una alternativa a FormGroup
para gestionar cualquier número de controles sin nombre.
Al igual que con las instancias de FormGroup
, puedes insertar y eliminar controles dinámicamente de las instancias de FormArray
, y el valor de la instancia de FormArray
y el estado de validación se calcula desde sus controles hijos.
Sin embargo, no necesitas definir una clave para cada control por nombre, así que esta es una gran opción si no conoces el número de valores hijos de antemano.
Para definir un formulario dinámico, sigue estos pasos.
- Importar la clase
FormArray
. - Definir un control
FormArray
. - Acceder al control
FormArray
con un método getter. - Mostrar el array de formulario en una plantilla.
El siguiente ejemplo te muestra cómo gestionar un array de alias en ProfileEditor
.
-
Importar la clase
FormArray
Importa la clase
FormArray
de@angular/forms
para usar para información de tipo. El servicioFormBuilder
está listo para crear una instancia deFormArray
.src/app/profile-editor/profile-editor.component.ts (import)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {FormArray} from '@angular/forms';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: [''], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); }}
-
Definir un control
FormArray
Puedes inicializar un array de formulario con cualquier número de controles, desde cero hasta muchos, definiéndolos en un array. Agrega una propiedad
aliases
a la instancia deFormGroup
paraprofileForm
para definir el array de formulario.Usa el método
FormBuilder.array()
para definir el array, y el métodoFormBuilder.control()
para poblar el array con un control inicial.src/app/profile-editor/profile-editor.component.ts (aliases form array)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
El control de alias dentro de la instancia de
FormGroup
ahora está poblado con un control único hasta que se agreguen más controles dinámicamente. -
Acceder al control
FormArray
Un getter proporciona acceso a los alias en la instancia de
FormArray
comparado con repetir el métodoprofileForm.get()
para obtener cada instancia. La instancia deFormArray
representa un número indefinido de controles en un array. Es conveniente acceder a un control a través de un getter, y este enfoque es directo de repetir para controles adicionales.Usa la sintaxis getter para crear una propiedad de clase
aliases
para recuperar el control delFormArray
de alias del grupo de formulario padre.src/app/profile-editor/profile-editor.component.ts (aliases getter)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
Debido a que el control devuelto es del tipo
AbstractControl
, necesitas proporcionar un tipo explícito para acceder a la sintaxis del método para la instancia deFormArray
. Define un método para insertar dinámicamente un control de alias en el array de formulario de alias. El métodoFormArray.push()
inserta el control como un nuevo elemento en el array.src/app/profile-editor/profile-editor.component.ts (add alias)
import {Component, inject} from '@angular/core';import {FormBuilder, ReactiveFormsModule} from '@angular/forms';import {Validators} from '@angular/forms';import {FormArray} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({ selector: 'app-profile-editor', templateUrl: './profile-editor.component.html', styleUrls: ['./profile-editor.component.css'], imports: [ReactiveFormsModule, JsonPipe],})export class ProfileEditorComponent { private formBuilder = inject(FormBuilder); profileForm = this.formBuilder.group({ firstName: ['', Validators.required], lastName: [''], address: this.formBuilder.group({ street: [''], city: [''], state: [''], zip: [''], }), aliases: this.formBuilder.array([this.formBuilder.control('')]), }); get aliases() { return this.profileForm.get('aliases') as FormArray; } updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street', }, }); } addAlias() { this.aliases.push(this.formBuilder.control('')); } onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }}
En la plantilla, cada control se muestra como un campo de entrada separado.
-
Mostrar el array de formulario en la plantilla
Para adjuntar los alias de tu modelo de formulario, debes agregarlo a la plantilla. Similar a la entrada
formGroupName
proporcionada porFormGroupNameDirective
,formArrayName
vincula la comunicación desde la instancia deFormArray
a la plantilla conFormArrayNameDirective
.Agrega el siguiente HTML de plantilla después del
<div>
que cierra el elementoformGroupName
.src/app/profile-editor/profile-editor.component.html (aliases form array template)
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()"> <label for="first-name">First Name: </label> <input id="first-name" type="text" formControlName="firstName" required /> <label for="last-name">Last Name: </label> <input id="last-name" type="text" formControlName="lastName" /> <div formGroupName="address"> <h2>Address</h2> <label for="street">Street: </label> <input id="street" type="text" formControlName="street" /> <label for="city">City: </label> <input id="city" type="text" formControlName="city" /> <label for="state">State: </label> <input id="state" type="text" formControlName="state" /> <label for="zip">Zip Code: </label> <input id="zip" type="text" formControlName="zip" /> </div> <div formArrayName="aliases"> <h2>Aliases</h2> <button type="button" (click)="addAlias()">+ Add another alias</button> @for (alias of aliases.controls; track $index; let i = $index) { <div> <!-- The repeated alias template --> <label for="alias-{{ i }}">Alias:</label> <input id="alias-{{ i }}" type="text" [formControlName]="i" /> </div> } </div> <p>Complete the form to enable button.</p> <button type="submit" [disabled]="!profileForm.valid">Submit</button></form><hr><p>Form Value: {{ profileForm.value | json }}</p><p>Form Status: {{ profileForm.status }}</p><button type="button" (click)="updateProfile()">Update Profile</button>
El bloque
@for
itera sobre cada instancia deFormControl
proporcionada por la instancia deFormArray
de alias. Debido a que los elementos delFormArray
no tienen nombre, asignas el índice a la variablei
y lo pasas a cada control para vincularlo a la entradaformControlName
.Cada vez que se agrega una nueva instancia de alias, la nueva instancia de
FormArray
se proporciona su control basado en el índice. Esto te permite rastrear cada control individual al calcular el estado y valor del control raíz. -
Agregar un alias
Inicialmente, el formulario contiene un campo
Alias
. Para agregar otro campo, haz clic en el botón Add Alias. También puedes validar el array de alias reportado por el modelo del formulario mostrado porForm Value
en la parte inferior de la plantilla. En lugar de una instancia deFormControl
para cada alias, puedes componer otra instancia deFormGroup
con campos adicionales. El proceso de definir un control para cada elemento es el mismo.
Resumen de API de formularios reactivos
La siguiente tabla lista las clases base y servicios usados para crear y gestionar controles de formulario reactivo. Para detalles completos de sintaxis, consulta la documentación de referencia de API para el paquete Forms.
Clases
Clase | Detailles |
---|---|
AbstractControl |
La clase base abstracta para las clases concretas de control de formulario FormControl , FormGroup , y FormArray . Proporciona sus comportamientos y propiedades comunes. |
FormControl |
Gestiona el valor y estado de validez de un control de formulario individual. Corresponde a un control de formulario HTML como <input> o <select> . |
FormGroup |
Gestiona el valor y estado de validez de un grupo de instancias de AbstractControl . Las propiedades del grupo incluyen sus controles hijos. El formulario de nivel superior en tu componente es FormGroup . |
FormArray |
Gestiona el valor y estado de validez de un array indexado numéricamente de instancias de AbstractControl . |
FormBuilder |
Un servicio inyectable que proporciona métodos de fábrica para crear instancias de control. |
FormRecord |
Rastrea el valor y estado de validez de una colección de instancias de FormControl , cada una de las cuales tiene el mismo tipo de valor. |
Directivas
Directiva | Detalles |
---|---|
FormControlDirective |
Sincroniza una instancia FormControl independiente a un elemento de control de formulario. |
FormControlName |
Sincroniza FormControl en una instancia existente de FormGroup a un elemento de control de formulario por nombre. |
FormGroupDirective |
Sincroniza una instancia existente de FormGroup a un elemento DOM. |
FormGroupName |
Sincroniza una instancia anidada de FormGroup a un elemento DOM. |
FormArrayName |
Sincroniza una instancia anidada de FormArray a un elemento DOM. |