Reactive forms fornecem uma abordagem model-driven para lidar com entradas de formulário cujos valores mudam ao longo do tempo. Este guia mostra como criar e atualizar um form control básico, progredir para o uso de vários controls em um grupo, validar valores de formulário e criar formulários dinâmicos onde você pode adicionar ou remover controls em tempo de execução.
Visão geral de reactive forms
Reactive forms usam uma abordagem explícita e imutável para gerenciar o estado de um formulário em um determinado momento. Cada mudança no estado do formulário retorna um novo estado, que mantém a integridade do modelo entre as mudanças. Reactive forms são construídos em torno de observable streams, onde entradas e valores de formulário são fornecidos como streams de valores de entrada, que podem ser acessados de forma síncrona.
Reactive forms também fornecem um caminho direto para testes porque você tem a garantia de que seus dados são consistentes e previsíveis quando solicitados. Quaisquer consumidores dos streams têm acesso para manipular esses dados com segurança.
Reactive forms diferem de template-driven forms de maneiras distintas. Reactive forms fornecem acesso síncrono ao data model, imutabilidade com operadores observable e rastreamento de mudanças através de observable streams.
Template-driven forms permitem acesso direto para modificar dados no seu template, mas são menos explícitos do que reactive forms porque dependem de directives incorporadas no template, juntamente com dados mutáveis para rastrear mudanças de forma assíncrona. Consulte a Visão Geral de Forms para comparações detalhadas entre os dois paradigmas.
Adicionando um form control básico
Existem três etapas para usar form controls.
- Gere um novo component e registre o módulo de reactive forms. Este módulo declara as directives de reactive-form que você precisa para usar reactive forms.
- Instancie um novo
FormControl. - Registre o
FormControlno template.
Você pode então exibir o formulário adicionando o component ao template.
Os exemplos a seguir mostram como adicionar um único form control. No exemplo, o usuário insere seu nome em um campo de entrada, captura esse valor de entrada e exibe o valor atual do elemento form control.
-
Gere um novo component e importe o ReactiveFormsModule
Use o comando CLI
ng generate componentpara gerar um component no seu projeto e importeReactiveFormsModuledo pacote@angular/formse adicione-o ao arrayimportsdo seu Component.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'); }} -
Declare uma instância FormControl
Use o construtor de
FormControlpara definir seu valor inicial, que neste caso é uma string vazia. Ao criar esses controls na classe do seu component, você obtém acesso imediato para ouvir, atualizar e validar o estado da entrada de formulário.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'); }} -
Registre o control no template
Depois de criar o control na classe do component, você deve associá-lo a um elemento form control no template. Atualize o template com o form control usando o binding
formControlfornecido porFormControlDirective, que também está incluído noReactiveFormsModule.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 a sintaxe de template binding, o form control agora está registrado no elemento de entrada
nameno template. O form control e o elemento DOM se comunicam entre si: a view reflete mudanças no model e o model reflete mudanças na view. -
Exiba o component
O
FormControlatribuído à propriedadenameé exibido quando o component<app-name-editor>é adicionado a um template.src/app/app.component.html (name editor)
<h1>Reactive Forms</h1><app-name-editor /><app-profile-editor />
Exibindo o valor de um form control
Você pode exibir o valor das seguintes maneiras.
- Através do observable
valueChanges, onde você pode ouvir mudanças no valor do formulário no template usandoAsyncPipeou na classe do component usando o métodosubscribe() - Com a propriedade
value, que fornece um snapshot do valor atual
O exemplo a seguir mostra como exibir o valor atual usando interpolação no template.
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>
O valor exibido muda conforme você atualiza o elemento form control.
Reactive forms fornecem acesso a informações sobre um determinado control através de propriedades e métodos fornecidos com cada instância. Essas propriedades e métodos da classe subjacente AbstractControl são usados para controlar o estado do formulário e determinar quando exibir mensagens ao lidar com validação de entrada.
Leia sobre outras propriedades e métodos de FormControl na Referência da API.
Substituindo o valor de um form control
Reactive forms têm métodos para alterar o valor de um control programaticamente, o que oferece a flexibilidade de atualizar o valor sem interação do usuário.
Uma instância de form control fornece um método setValue() que atualiza o valor do form control e valida a estrutura do valor fornecido em relação à estrutura do control.
Por exemplo, ao recuperar dados de formulário de uma API ou serviço de backend, use o método setValue() para atualizar o control com seu novo valor, substituindo o valor antigo inteiramente.
O exemplo a seguir adiciona um método à classe do component para atualizar o valor do control para Nancy usando o método setValue().
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'); }}
Atualize o template com um botão para simular uma atualização de nome. Quando você clica no botão Update Name, o valor inserido no elemento form control é refletido como seu valor atual.
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>
O form model é a fonte da verdade para o control, então quando você clica no botão, o valor da entrada é alterado dentro da classe do component, sobrescrevendo seu valor atual.
HELPFUL: Neste exemplo, você está usando um único control.
Ao usar o método setValue() com um form group ou form array, o valor precisa corresponder à estrutura do grupo ou array.
Agrupando form controls
Os formulários normalmente contêm vários controls relacionados. Reactive forms fornecem duas maneiras de agrupar vários controls relacionados em um único formulário de entrada.
| Form groups | Detalhes |
|---|---|
| Form group | Define um formulário com um conjunto fixo de controls que você pode gerenciar juntos. Os fundamentos do form group são discutidos nesta seção. Você também pode aninhar form groups para criar formulários mais complexos. |
| Form array | Define um formulário dinâmico, onde você pode adicionar e remover controls em tempo de execução. Você também pode aninhar form arrays para criar formulários mais complexos. Para mais sobre esta opção, consulte Criando formulários dinâmicos. |
Assim como uma instância de form control oferece controle sobre um único campo de entrada, uma instância de form group rastreia o estado do formulário de um grupo de instâncias de form control (por exemplo, um formulário). Cada control em uma instância de form group é rastreado por nome ao criar o form group. O exemplo a seguir mostra como gerenciar várias instâncias de form control em um único grupo.
Gere um component ProfileEditor e importe as classes FormGroup e FormControl do pacote @angular/forms.
ng generate component ProfileEditor
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 adicionar um form group a este component, siga as seguintes etapas.
-
Crie uma instância FormGroup
Crie uma propriedade na classe do component chamada
profileForme defina a propriedade como uma nova instância de form group. Para inicializar o form group, forneça ao construtor um objeto de chaves nomeadas mapeadas para seus controls.Para o formulário de perfil, adicione duas instâncias de form control com os nomes
firstNameelastNameprofile-editor.component.ts (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', }, }); }}Os form controls individuais agora estão coletados dentro de um grupo. Uma instância
FormGroupfornece seu valor de modelo como um objeto reduzido dos valores de cada control no grupo. Uma instância de form group tem as mesmas propriedades (comovalueeuntouched) e métodos (comosetValue()) que uma instância de form control. -
Associe o model FormGroup e a view
Um form group rastreia o status e as mudanças de cada um de seus controls, portanto, se um dos controls mudar, o control pai também emite um novo status ou mudança de valor. O model para o grupo é mantido a partir de seus membros. Depois de definir o model, você deve atualizar o template para refletir o model na view.
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>Assim como um form group contém um grupo de controls, o
FormGroupprofileForm é vinculado ao elementoformcom a directiveFormGroup, criando uma camada de comunicação entre o model e o formulário contendo as entradas. A entradaformControlNamefornecida pela directiveFormControlNamevincula cada entrada individual ao form control definido noFormGroup. Os form controls se comunicam com seus respectivos elementos. Eles também comunicam mudanças à instância do form group, que fornece a fonte da verdade para o valor do modelo. -
Salve os dados do formulário
O component
ProfileEditoraceita entrada do usuário, mas em um cenário real você deseja capturar o valor do formulário e disponibilizá-lo para processamento adicional fora do component. A directiveFormGroupescuta o eventosubmitemitido pelo elementoforme emite um eventongSubmitao qual você pode vincular uma função de callback. Adicione um event listenerngSubmità tagformcom o método de callbackonSubmit().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>O método
onSubmit()no componentProfileEditorcaptura o valor atual deprofileForm. UseEventEmitterpara manter o formulário encapsulado e fornecer o valor do formulário fora do component. O exemplo a seguir usaconsole.warnpara registrar uma mensagem no console do navegador.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); }}O evento
submité emitido pela tagformusando o evento DOM nativo. Você aciona o evento clicando em um botão com tiposubmit. Isso permite que o usuário pressione a tecla Enter para enviar o formulário concluído.Use um elemento
buttonpara adicionar um botão na parte inferior do formulário para acionar o envio do formulário.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>O botão no trecho anterior também tem um binding
disabledanexado a ele para desabilitar o botão quandoprofileFormfor inválido. Você ainda não está executando nenhuma validação, então o botão está sempre ativado. A validação básica de formulário é abordada na seção Validando entrada de formulário. -
Exiba o component
Para exibir o component
ProfileEditorque contém o formulário, adicione-o a um template de component.app.component.html (profile editor)
<h1>Reactive Forms</h1><app-name-editor /><app-profile-editor />ProfileEditorpermite que você gerencie as instâncias de form control para os controlsfirstNameelastNamedentro da instância do form group.Criando form groups aninhados
Form groups podem aceitar tanto instâncias individuais de form control quanto outras instâncias de form group como filhos. Isso torna a composição de modelos de formulário complexos mais fácil de manter e agrupar logicamente.
Ao construir formulários complexos, gerenciar as diferentes áreas de informação é mais fácil em seções menores. Usar uma instância de form group aninhado permite que você divida form groups grandes em grupos menores e mais gerenciáveis.
Para criar formulários mais complexos, use as seguintes etapas.
- Crie um grupo aninhado.
- Agrupe o formulário aninhado no template.
Alguns tipos de informação naturalmente se enquadram no mesmo grupo. Um nome e endereço são exemplos típicos de tais grupos aninhados e são usados nos exemplos a seguir.
Para criar um grupo aninhado em `profileForm`, adicione um elemento `address` aninhado à instância do form group. 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', }, }); }}Neste exemplo,
address groupcombina os controls atuaisfirstNameelastNamecom os novos controlsstreet,city,stateezip. Embora o elementoaddressno form group seja um filho do elementoprofileFormgeral no form group, as mesmas regras se aplicam com mudanças de valor e status. As mudanças no status e valor do form group aninhado se propagam para o form group pai, mantendo a consistência com o modelo geral. -
Agrupe o formulário aninhado no template
Depois de atualizar o model na classe do component, atualize o template para conectar a instância do form group e seus elementos de entrada. Adicione o form group
addresscontendo os camposstreet,city,stateezipao templateProfileEditor.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>O formulário
ProfileEditoré exibido como um grupo, mas o modelo é dividido ainda mais para representar as áreas de agrupamento lógico.Exiba o valor da instância do form group no template do component usando a propriedade
valueeJsonPipe.
Atualizando partes do data model
Ao atualizar o valor de uma instância de form group que contém vários controls, você pode querer atualizar apenas partes do modelo. Esta seção cobre como atualizar partes específicas de um data model de form control.
Existem duas maneiras de atualizar o valor do modelo:
| Métodos | Detalhes |
|---|---|
setValue() |
Define um novo valor para um control individual. O método setValue() adere estritamente à estrutura do form group e substitui todo o valor do control. |
patchValue() |
Substitui quaisquer propriedades definidas no objeto que foram alteradas no form model. |
As verificações estritas do método setValue() ajudam a capturar erros de aninhamento em formulários complexos, enquanto patchValue() falha silenciosamente nesses erros.
Em ProfileEditorComponent, use o método updateProfile com o exemplo a seguir para atualizar o primeiro nome e endereço de rua do usuário.
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', }, }); }}
Simule uma atualização adicionando um botão ao template para atualizar o perfil do usuário sob demanda.
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>
Quando um usuário clica no botão, o model profileForm é atualizado com novos valores para firstName e street. Observe que street é fornecido em um objeto dentro da propriedade address.
Isso é necessário porque o método patchValue() aplica a atualização contra a estrutura do modelo.
PatchValue() atualiza apenas as propriedades que o form model define.
Usando o serviço FormBuilder para gerar controls
Criar instâncias de form control manualmente pode se tornar repetitivo ao lidar com vários formulários.
O serviço FormBuilder fornece métodos convenientes para gerar controls.
Use as seguintes etapas para aproveitar este serviço.
- Importe a classe
FormBuilder. - Injete o serviço
FormBuilder. - Gere o conteúdo do formulário.
Os exemplos a seguir mostram como refatorar o component ProfileEditor para usar o serviço form builder para criar instâncias de form control e form group.
-
Importe a classe FormBuilder
Importe a classe
FormBuilderdo pacote@angular/forms.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('')); }} -
Injete o serviço FormBuilder
O serviço
FormBuilderé um provider injetável do módulo de reactive forms. Use a funçãoinject()para injetar essa dependência no seu component.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('')); }} -
Gere form controls
O serviço
FormBuildertem três métodos:control(),group()earray(). Estes são métodos de fábrica para gerar instâncias nas classes de seus components, incluindo form controls, form groups e form arrays. Use o métodogrouppara criar os controlsprofileForm.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('')); }}No exemplo anterior, você usa o método
group()com o mesmo objeto para definir as propriedades no model. O valor para cada nome de control é um array contendo o valor inicial como o primeiro item no array.TIP: Você pode definir o control apenas com o valor inicial, mas se seus controls precisarem de validação síncrona ou assíncrona, adicione validadores síncronos e assíncronos como segundo e terceiro itens no array. Compare o uso do form builder com a criação manual das instâncias.
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', }, }); }}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 entrada de formulário
Validação de formulário é usada para garantir que a entrada do usuário esteja completa e correta. Esta seção cobre adicionar um único validador a um form control e exibir o status geral do formulário. A validação de formulário é abordada mais extensivamente no guia Validação de Formulário.
Use as seguintes etapas para adicionar validação de formulário.
- Importe uma função validadora no seu component de formulário.
- Adicione o validador ao campo no formulário.
- Adicione lógica para lidar com o status de validação.
A validação mais comum é tornar um campo obrigatório.
O exemplo a seguir mostra como adicionar uma validação obrigatória ao control firstName e exibir o resultado da validação.
-
Importe uma função validadora
Reactive forms incluem um conjunto de funções validadoras para casos de uso comuns. Essas funções recebem um control para validar e retornam um objeto de erro ou um valor nulo com base na verificação de validação.
Importe a classe
Validatorsdo pacote@angular/forms.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); }} -
Torne um campo obrigatório
No component
ProfileEditor, adicione o método estáticoValidators.requiredcomo o segundo item no array para o controlfirstName.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); }} -
Exiba o status do formulário
Quando você adiciona um campo obrigatório ao form control, seu status inicial é inválido. Este status inválido se propaga para o elemento form group pai, tornando seu status inválido. Acesse o status atual da instância do form group através de sua propriedade
status.Exiba o status atual de
profileFormusando interpolação.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>O botão Submit está desabilitado porque
profileFormé inválido devido ao control de formuláriofirstNameobrigatório. Depois de preencher a entradafirstName, o formulário se torna válido e o botão Submit é habilitado.Para mais sobre validação de formulário, visite o guia Validação de Formulário.
Criando formulários dinâmicos
FormArray é uma alternativa a FormGroup para gerenciar qualquer número de controls sem nome.
Assim como nas instâncias de form group, você pode inserir e remover dinamicamente controls de instâncias de form array, e o valor da instância de form array e o status de validação são calculados a partir de seus controls filhos.
No entanto, você não precisa definir uma chave para cada control por nome, então esta é uma ótima opção se você não sabe o número de valores filhos antecipadamente.
Para definir um formulário dinâmico, siga as seguintes etapas.
- Importe a classe
FormArray. - Defina um control
FormArray. - Acesse o control
FormArraycom um método getter. - Exiba o form array em um template.
O exemplo a seguir mostra como gerenciar um array de aliases em ProfileEditor.
-
Importe a classe
FormArrayImporte a classe
FormArrayde@angular/formspara usar para informações de tipo. O serviçoFormBuilderestá pronto para criar uma instânciaFormArray.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('')); }} -
Defina um control
FormArrayVocê pode inicializar um form array com qualquer número de controls, de zero a muitos, definindo-os em um array. Adicione uma propriedade
aliasesà instância do form group paraprofileFormpara definir o form array.Use o método
FormBuilder.array()para definir o array e o métodoFormBuilder.control()para preencher o array com um control inicial.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); }}O control aliases na instância do form group agora está preenchido com um único control até que mais controls sejam adicionados dinamicamente.
-
Acesse o control
FormArrayUm getter fornece acesso aos aliases na instância do form array em comparação com repetir o método
profileForm.get()para obter cada instância. A instância do form array representa um número indefinido de controls em um array. É conveniente acessar um control através de um getter, e esta abordagem é direta de repetir para controls adicionais.Use a sintaxe getter para criar uma propriedade de classe
aliasespara recuperar o control de form array de aliases do form group pai.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); }}Como o control retornado é do tipo
AbstractControl, você precisa fornecer um tipo explícito para acessar a sintaxe do método para a instância do form array. Defina um método para inserir dinamicamente um control de alias no form array de aliases. O métodoFormArray.push()insere o control como um novo item no array, e você também pode passar um array de controls para FormArray.push() para registrar vários controls de uma vez.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); }}No template, cada control é exibido como um campo de entrada separado.
-
Exiba o form array no template
Para anexar os aliases do seu form model, você deve adicioná-lo ao template. Semelhante à entrada
formGroupNamefornecida porFormGroupNameDirective,formArrayNamevincula a comunicação da instância do form array ao template comFormArrayNameDirective.Adicione o seguinte HTML de template após a
<div>que fecha o elementoformGroupName.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>O bloco
@foritera sobre cada instância de form control fornecida pela instância do form array de aliases. Como os elementos do form array não têm nome, você atribui o índice à variávelie o passa para cada control para vinculá-lo à entradaformControlName.Cada vez que uma nova instância de alias é adicionada, a nova instância do form array é fornecida com seu control com base no índice. Isso permite que você rastreie cada control individual ao calcular o status e o valor do control raiz.
-
Adicione um alias
Inicialmente, o formulário contém um campo
Alias. Para adicionar outro campo, clique no botão Add Alias. Você também pode validar o array de aliases relatado pelo form model exibido porForm Valuena parte inferior do template. Em vez de uma instância de form control para cada alias, você pode compor outra instância de form group com campos adicionais. O processo de definir um control para cada item é o mesmo.
Eventos unificados de mudança de estado de control
Todos os form controls expõem um único stream unificado de eventos de mudança de estado de control através do observable events em AbstractControl (FormControl, FormGroup, FormArray e FormRecord).
Este stream unificado permite que você reaja a mudanças de estado de value, status, pristine, touched e reset e também para ações no nível de formulário, como submit, permitindo que você lide com todas as atualizações com uma única assinatura em vez de conectar vários observables.
Tipos de evento
Cada item emitido por events é uma instância de uma classe de evento específica:
ValueChangeEvent— quando o value do control muda.StatusChangeEvent— quando o status de validação do control atualiza para um dos valoresFormControlStatus(VALID,INVALID,PENDINGouDISABLED).PristineChangeEvent— quando o estado pristine/dirty do control muda.TouchedChangeEvent— quando o estado touched/untouched do control muda.FormResetEvent— quando um control ou formulário é resetado, seja através da APIreset()ou de uma ação nativa.FormSubmittedEvent— quando o formulário é enviado.
Todas as classes de evento estendem ControlEvent e incluem uma referência source ao AbstractControl que originou a mudança, o que é útil em formulários grandes.
import { Component } from '@angular/core';import { FormControl, ValueChangeEvent, StatusChangeEvent, PristineChangeEvent, TouchedChangeEvent, FormResetEvent, FormSubmittedEvent, ReactiveFormsModule, FormGroup,} from '@angular/forms';@Component({/* ... */ })export class UnifiedEventsBasicComponent { form = new FormGroup({ username: new FormControl(''), }); constructor() { this.form.events.subscribe((e) => { if (e instanceof ValueChangeEvent) { console.log('Value changed to: ', e.value); } if (e instanceof StatusChangeEvent) { console.log('Status changed to: ', e.status); } if (e instanceof PristineChangeEvent) { console.log('Pristine status changed to: ', e.pristine); } if (e instanceof TouchedChangeEvent) { console.log('Touched status changed to: ', e.touched); } if (e instanceof FormResetEvent) { console.log('Form was reset'); } if (e instanceof FormSubmittedEvent) { console.log('Form was submitted'); } }); }}
Filtrando eventos específicos
Prefira operadores RxJS quando você precisar apenas de um subconjunto de tipos de evento.
import { filter } from 'rxjs/operators';import { StatusChangeEvent } from '@angular/forms';control.events .pipe(filter((e) => e instanceof StatusChangeEvent)) .subscribe((e) => console.log('Status:', e.status));
Unificando de múltiplas assinaturas
Antes
import { combineLatest } from 'rxjs/operators';combineLatest([control.valueChanges, control.statusChanges]) .subscribe(([value, status]) => { /* ... */ });
Depois
control.events.subscribe((e) => { // Handle ValueChangeEvent, StatusChangeEvent, etc.});
NOTE: Na mudança de valor, a emissão acontece logo após um valor deste control ser atualizado. O valor de um control pai (por exemplo, se este FormControl faz parte de um FormGroup) é atualizado mais tarde, portanto, acessar um valor de um control pai (usando a propriedade value) do callback deste evento pode resultar em obter um valor que ainda não foi atualizado. Assine os events do control pai em vez disso.
Funções utilitárias para restringir tipos de form control {#creating-nested-form-groups 'See more about nesting groups'} {#validating-form-input 'Learn more about validating form input'}
O Angular fornece quatro funções utilitárias que ajudam a determinar o tipo concreto de um AbstractControl. Essas funções atuam como type guards e restringem o tipo de control quando retornam true, o que permite que você acesse com segurança propriedades específicas do subtipo dentro do mesmo bloco.
| Função utilitária | Detalhes |
|---|---|
isFormControl |
Retorna true quando o control é um FormControl. |
isFormGroup |
Retorna true quando o control é um FormGroup |
isFormRecord |
Retorna true quando o control é um FormRecord |
isFormArray |
Retorna true quando o control é um FormArray |
Esses helpers são particularmente úteis em validadores personalizados, onde a assinatura da função recebe um AbstractControl, mas a lógica é destinada a um tipo específico de control.
import { AbstractControl, isFormArray } from '@angular/forms';export function positiveValues(control: AbstractControl) { if (!isFormArray(control)) { return null; // Not a FormArray: validator is not applicable. } // Safe to access FormArray-specific API after narrowing. const hasNegative = control.controls.some(c => c.value < 0); return hasNegative ? { positiveValues: true } : null;}
Resumo da API de reactive forms
A tabela a seguir lista as classes base e serviços usados para criar e gerenciar reactive form controls. Para detalhes de sintaxe completos, consulte a documentação de referência da API para o pacote Forms.
Classes
| Classe | Detalhes |
|---|---|
AbstractControl |
A classe base abstrata para as classes concretas de form control FormControl, FormGroup e FormArray. Ela fornece seus comportamentos e propriedades comuns. |
FormControl |
Gerencia o valor e o status de validação de um form control individual. Ele corresponde a um form control HTML, como <input> ou <select>. |
FormGroup |
Gerencia o valor e o estado de validação de um grupo de instâncias AbstractControl. As propriedades do grupo incluem seus controls filhos. O formulário de nível superior no seu component é FormGroup. |
FormArray |
Gerencia o valor e o estado de validação de um array numericamente indexado de instâncias AbstractControl. |
FormBuilder |
Um serviço injetável que fornece métodos de fábrica para criar instâncias de control. |
FormRecord |
Rastreia o valor e o estado de validação de uma coleção de instâncias FormControl, cada uma das quais tem o mesmo tipo de valor. |
Directives
| Directive | Detalhes |
|---|---|
FormControlDirective |
Sincroniza uma instância FormControl autônoma a um elemento form control. |
FormControlName |
Sincroniza FormControl em uma instância FormGroup existente a um elemento form control por nome. |
FormGroupDirective |
Sincroniza uma instância FormGroup existente a um elemento DOM. |
FormGroupName |
Sincroniza uma instância FormGroup aninhada a um elemento DOM. |
FormArrayName |
Sincroniza uma instância FormArray aninhada a um elemento DOM. |