Guias Detalhados
Forms

Construindo um formulário template-driven

Este tutorial mostra como criar um formulário template-driven. Os elementos de controle no formulário são vinculados a propriedades de dados que possuem validação de entrada. A validação de entrada ajuda a manter a integridade dos dados e o estilo melhora a experiência do usuário.

Formulários template-driven usam vinculação de dados bidirecional para atualizar o modelo de dados no component conforme as alterações são feitas no template e vice-versa.

Template vs Reactive forms

Angular suporta duas abordagens de design para formulários interativos. Formulários template-driven permitem que você use directives específicas de formulário em seu template Angular. Formulários reactive fornecem uma abordagem orientada a modelo para construção de formulários.

Formulários template-driven são uma ótima escolha para formulários pequenos ou simples, enquanto formulários reactive são mais escaláveis e adequados para formulários complexos. Para uma comparação das duas abordagens, veja Escolhendo uma abordagem

Você pode construir praticamente qualquer tipo de formulário com um template Angular — formulários de login, formulários de contato e praticamente qualquer formulário de negócios. Você pode dispor os controles de forma criativa e vinculá-los aos dados no seu modelo de objeto. Você pode especificar regras de validação e exibir erros de validação, permitir condicionalmente entrada de controles específicos, acionar feedback visual integrado e muito mais.

Objetivos

Este tutorial ensina como fazer o seguinte:

  • Construir um formulário Angular com um component e template
  • Usar ngModel para criar vinculações de dados bidirecionais para leitura e escrita de valores de controle de entrada
  • Fornecer feedback visual usando classes CSS especiais que rastreiam o estado dos controles
  • Exibir erros de validação aos usuários e permitir condicionalmente entrada de controles de formulário com base no status do formulário
  • Compartilhar informações entre elementos HTML usando variáveis de referência de template

Construir um formulário template-driven

Formulários template-driven dependem de directives definidas no FormsModule.

Directives Detalhes
NgModel Reconcilia mudanças de valor no elemento de formulário anexado com mudanças no modelo de dados, permitindo que você responda à entrada do usuário com validação de entrada e tratamento de erros.
NgForm Cria uma instância FormGroup de nível superior e a vincula a um elemento <form> para rastrear o valor agregado do formulário e o status de validação. Assim que você importa o FormsModule, esta directive se torna ativa por padrão em todas as tags <form>. Você não precisa adicionar um seletor especial.
NgModelGroup Cria e vincula uma instância FormGroup a um elemento DOM.

Visão geral das etapas

No decorrer deste tutorial, você vincula um formulário de exemplo aos dados e lida com a entrada do usuário usando as seguintes etapas.

  1. Construir o formulário básico.
    • Definir um modelo de dados de exemplo
    • Incluir a infraestrutura necessária, como o FormsModule
  2. Vincular controles de formulário a propriedades de dados usando a directive ngModel e a sintaxe de vinculação de dados bidirecional.
    • Examinar como ngModel relata estados de controle usando classes CSS
    • Nomear controles para torná-los acessíveis ao ngModel
  3. Rastrear a validade de entrada e o status do controle usando ngModel.
    • Adicionar CSS personalizado para fornecer feedback visual sobre o status
    • Mostrar e ocultar mensagens de erro de validação
  4. Responder a um evento de clique de botão HTML nativo adicionando aos dados do modelo.
  5. Lidar com o envio do formulário usando a propriedade de output ngSubmit do formulário.
    • Desabilitar o botão Submit até que o formulário seja válido
    • Após o envio, trocar o formulário concluído por conteúdo diferente na página

Construir o formulário

  1. O aplicativo de exemplo fornecido cria a classe Actor que define o modelo de dados refletido no formulário.

actor.ts

export class Actor {  constructor(    public id: number,    public name: string,    public skill: string,    public studio?: string,  ) {}}
  1. O layout e os detalhes do formulário são definidos na classe ActorFormComponent.

    actor-form.component.ts (v1)

    import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}

    O valor do selector do component "app-actor-form" significa que você pode colocar este formulário em um template pai usando a tag <app-actor-form>.

  2. O código a seguir cria uma nova instância de actor, para que o formulário inicial possa mostrar um actor de exemplo.

    import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}

    Esta demonstração usa dados fictícios para model e skills. Em um aplicativo real, você injetaria um service de dados para obter e salvar dados reais, ou exporia essas propriedades como inputs e outputs.

  3. O component habilita o recurso de Forms importando o módulo FormsModule.

    import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}
  4. O formulário é exibido no layout do aplicativo definido pelo template do component raiz.

    src/app/app.component.html

    <app-actor-form />

    O template inicial define o layout para um formulário com dois grupos de formulário e um botão de envio. Os grupos de formulário correspondem a duas propriedades do modelo de dados Actor, name e studio. Cada grupo tem um rótulo e uma caixa para entrada do usuário.

    • O elemento de controle <input> de Name tem o atributo HTML5 required
    • O elemento de controle <input> de Studio não tem porque studio é opcional

    O botão Submit tem algumas classes nele para estilização. Neste ponto, o layout do formulário é todo HTML5 simples, sem vinculações ou directives.

  5. O formulário de exemplo usa algumas classes de estilo do Twitter Bootstrap: container, form-group, form-control e btn. Para usar esses estilos, a folha de estilo do aplicativo importa a biblioteca.

src/styles.css

@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
  1. O formulário requer que a habilidade de um actor seja escolhida de uma lista predefinida de skills mantida internamente em ActorFormComponent. O loop @for do Angular itera sobre os valores de dados para preencher o elemento <select>.

actor-form.component.html (skills)

<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

Se você executar o aplicativo agora, verá a lista de habilidades no controle de seleção. Os elementos de entrada ainda não estão vinculados a valores de dados ou eventos, portanto ainda estão em branco e não têm comportamento.

Vincular controles de entrada a propriedades de dados

O próximo passo é vincular os controles de entrada às propriedades Actor correspondentes com vinculação de dados bidirecional, para que eles respondam à entrada do usuário atualizando o modelo de dados e também respondam a mudanças programáticas nos dados atualizando a exibição.

A directive ngModel declarada no FormsModule permite vincular controles em seu formulário template-driven a propriedades em seu modelo de dados. Quando você inclui a directive usando a sintaxe para vinculação de dados bidirecional, [(ngModel)], o Angular pode rastrear o valor e a interação do usuário com o controle e manter a view sincronizada com o modelo.

  1. Edite o arquivo de template actor-form.component.html.
  2. Encontre a tag <input> ao lado do rótulo Name.
  3. Adicione a directive ngModel, usando a sintaxe de vinculação de dados bidirecional [(ngModel)]="...".

actor-form.component.html (excerpt)

<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

HELPFUL: Este exemplo tem uma interpolação de diagnóstico temporária após cada tag de entrada, {{model.name}}, para mostrar o valor de dados atual da propriedade correspondente. O comentário lembra você de remover as linhas de diagnóstico quando terminar de observar a vinculação de dados bidirecional em funcionamento.

Acessar o status geral do formulário

Quando você importou o FormsModule em seu component, o Angular criou e anexou automaticamente uma directive NgForm à tag <form> no template (porque NgForm tem o seletor form que corresponde aos elementos <form>).

Para obter acesso ao NgForm e ao status geral do formulário, declare uma variável de referência de template.

  1. Edite o arquivo de template actor-form.component.html.

  2. Atualize a tag <form> com uma variável de referência de template, #actorForm, e defina seu valor da seguinte forma.

    actor-form.component.html (excerpt)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

    A variável de template actorForm agora é uma referência à instância da directive NgForm que governa o formulário como um todo.

  3. Execute o aplicativo.

  4. Comece a digitar na caixa de entrada Name.

    Conforme você adiciona e exclui caracteres, pode vê-los aparecer e desaparecer do modelo de dados.

A linha de diagnóstico que mostra valores interpolados demonstra que os valores estão realmente fluindo da caixa de entrada para o modelo e de volta novamente.

Nomear elementos de controle

Quando você usa [(ngModel)] em um elemento, deve definir um atributo name para esse elemento. O Angular usa o nome atribuído para registrar o elemento com a directive NgForm anexada ao elemento pai <form>.

O exemplo adicionou um atributo name ao elemento <input> e o definiu como "name", o que faz sentido para o nome do actor. Qualquer valor único serve, mas usar um nome descritivo é útil.

  1. Adicione vinculações [(ngModel)] e atributos name similares a Studio e Skill.
  2. Agora você pode remover as mensagens de diagnóstico que mostram valores interpolados.
  3. Para confirmar que a vinculação de dados bidirecional funciona para todo o modelo de actor, adicione uma nova vinculação de texto com o pipe json no topo do template do component, que serializa os dados para uma string.

Após essas revisões, o template do formulário deve se parecer com o seguinte:

actor-form.component.html (excerpt)

<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

Você notará que:

  • Cada elemento <input> tem uma propriedade id. Isso é usado pelo atributo for do elemento <label> para corresponder o rótulo ao seu controle de entrada. Este é um recurso HTML padrão.

  • Cada elemento <input> também tem a propriedade name necessária que o Angular usa para registrar o controle com o formulário.

Quando você tiver observado os efeitos, pode excluir a vinculação de texto {{ model | json }}.

Rastrear estados do formulário

O Angular aplica a classe ng-submitted aos elementos form após o formulário ter sido enviado. Esta classe pode ser usada para alterar o estilo do formulário após ele ter sido enviado.

Rastrear estados do controle

Adicionar a directive NgModel a um controle adiciona nomes de classe ao controle que descrevem seu estado. Essas classes podem ser usadas para alterar o estilo de um controle com base em seu estado.

A tabela a seguir descreve os nomes de classe que o Angular aplica com base no estado do controle.

Estados Classe se verdadeiro Classe se falso
O controle foi visitado. ng-touched ng-untouched
O valor do controle foi alterado. ng-dirty ng-pristine
O valor do controle é válido. ng-valid ng-invalid

O Angular também aplica a classe ng-submitted aos elementos form após o envio, mas não aos controles dentro do elemento form.

Você usa essas classes CSS para definir os estilos para seu controle com base em seu status.

Observar estados do controle

Para ver como as classes são adicionadas e removidas pelo framework, abra as ferramentas de desenvolvedor do navegador e inspecione o elemento <input> que representa o nome do actor.

  1. Usando as ferramentas de desenvolvedor do seu navegador, encontre o elemento <input> que corresponde à caixa de entrada Name. Você pode ver que o elemento tem várias classes CSS além de "form-control".

  2. Quando você o abre pela primeira vez, as classes indicam que ele tem um valor válido, que o valor não foi alterado desde a inicialização ou redefinição, e que o controle não foi visitado desde a inicialização ou redefinição.

    <input class="form-control ng-untouched ng-pristine ng-valid">;
  3. Execute as seguintes ações na caixa <input> de Name e observe quais classes aparecem.

    • Olhe mas não toque. As classes indicam que está intocado, pristine e válido.

    • Clique dentro da caixa de nome, depois clique fora dela. O controle foi visitado agora, e o elemento tem a classe ng-touched em vez da classe ng-untouched.

    • Adicione barras ao final do nome. Agora está touched e dirty.

    • Apague o nome. Isso torna o valor inválido, então a classe ng-invalid substitui a classe ng-valid.

Criar feedback visual para estados

O par ng-valid/ng-invalid é particularmente interessante, porque você quer enviar um sinal visual forte quando os valores são inválidos. Você também quer marcar campos obrigatórios.

Você pode marcar campos obrigatórios e dados inválidos ao mesmo tempo com uma barra colorida à esquerda da caixa de entrada.

Para alterar a aparência dessa forma, execute as seguintes etapas.

  1. Adicione definições para as classes CSS ng-*.
  2. Adicione essas definições de classe a um novo arquivo forms.css.
  3. Adicione o novo arquivo ao projeto como irmão de index.html:

src/assets/forms.css

.ng-valid[required], .ng-valid.required  {  border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form)  {  border-left: 5px solid #a94442; /* red */}
  1. No arquivo index.html, atualize a tag <head> para incluir a nova folha de estilo.

src/index.html (styles)

<!DOCTYPE html><html lang="en">  <head>    <title>Hero Form</title>    <base href="/">    <meta charset="UTF-8">    <meta name="viewport" content="width=device-width, initial-scale=1">    <link rel="stylesheet"          href="https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css">    <link rel="stylesheet" href="assets/forms.css">  </head>  <body>    <app-root></app-root>  </body></html>

Mostrar e ocultar mensagens de erro de validação

A caixa de entrada Name é obrigatória e limpá-la deixa a barra vermelha. Isso indica que algo está errado, mas o usuário não sabe o que está errado ou o que fazer a respeito. Você pode fornecer uma mensagem útil verificando e respondendo ao estado do controle.

A caixa de seleção Skill também é obrigatória, mas não precisa desse tipo de tratamento de erro porque a caixa de seleção já restringe a seleção a valores válidos.

Para definir e mostrar uma mensagem de erro quando apropriado, execute as seguintes etapas.

  1. Adicionar uma referência local à entrada

    Estenda a tag input com uma variável de referência de template que você pode usar para acessar o controle Angular da caixa de entrada de dentro do template. No exemplo, a variável é #name="ngModel".

    A variável de referência de template (#name) é definida como "ngModel" porque esse é o valor da propriedade NgModel.exportAs. Esta propriedade diz ao Angular como vincular uma variável de referência a uma directive.

  2. Adicionar a mensagem de erro

    Adicione um <div> que contém uma mensagem de erro adequada.

  3. Tornar a mensagem de erro condicional

    Mostre ou oculte a mensagem de erro vinculando propriedades do controle name à propriedade hidden do elemento <div> da mensagem.

  4. actor-form.component.html (hidden-error-msg)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>
  5. Adicionar uma mensagem de erro condicional ao name

    Adicione uma mensagem de erro condicional à caixa de entrada name, como no exemplo a seguir.

    actor-form.component.html (excerpt)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

Neste exemplo, você oculta a mensagem quando o controle está válido ou pristine. Pristine significa que o usuário não alterou o valor desde que foi exibido neste formulário. Se você ignorar o estado pristine, ocultará a mensagem apenas quando o valor for válido. Se você chegar neste component com um novo actor em branco ou um actor inválido, verá a mensagem de erro imediatamente, antes de ter feito qualquer coisa.

Você pode querer que a mensagem seja exibida apenas quando o usuário fizer uma alteração inválida. Ocultar a mensagem enquanto o controle está no estado pristine alcança esse objetivo. Você verá a importância dessa escolha quando adicionar um novo actor ao formulário na próxima etapa.

Adicionar um novo actor

Este exercício mostra como você pode responder a um evento de clique de botão HTML nativo adicionando aos dados do modelo. Para permitir que os usuários do formulário adicionem um novo actor, você adicionará um botão New Actor que responde a um evento de clique.

  1. No template, coloque um elemento <button> "New Actor" na parte inferior do formulário.
  2. No arquivo do component, adicione o método de criação de actor ao modelo de dados de actor.

actor-form.component.ts (New Actor method)

import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}
  1. Vincule o evento de clique do botão a um método de criação de actor, newActor().

actor-form.component.html (New Actor button)

<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>
  1. Execute o aplicativo novamente e clique no botão New Actor.

    O formulário é limpo e as barras obrigatórias à esquerda da caixa de entrada ficam vermelhas, indicando propriedades name e skill inválidas. Observe que as mensagens de erro estão ocultas. Isso ocorre porque o formulário está pristine; você ainda não alterou nada.

  2. Digite um nome e clique em New Actor novamente.

    Agora o aplicativo exibe uma mensagem de erro Name is required, porque a caixa de entrada não está mais pristine. O formulário lembra que você digitou um nome antes de clicar em New Actor.

  3. Para restaurar o estado pristine dos controles do formulário, limpe todos os flags imperativamente chamando o método reset() do formulário após chamar o método newActor().

    actor-form.component.html (Reset the form)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

    Agora clicar em New Actor redefine tanto o formulário quanto seus flags de controle.

Enviar o formulário com ngSubmit

O usuário deve ser capaz de enviar este formulário depois de preenchê-lo. O botão Submit na parte inferior do formulário não faz nada por si só, mas aciona um evento de envio de formulário por causa de seu tipo (type="submit").

Para responder a este evento, execute as seguintes etapas.

  1. Escutar ngOnSubmit

    Vincule a propriedade de evento ngSubmit do formulário ao método onSubmit() do component actor-form.

    actor-form.component.html (ngSubmit)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>
  2. Vincular a propriedade disabled

    Use a variável de referência de template, #actorForm para acessar o formulário que contém o botão Submit e criar uma vinculação de evento.

    Você vinculará a propriedade do formulário que indica sua validade geral à propriedade disabled do botão Submit.

    actor-form.component.html (submit-button)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>
  3. Executar o aplicativo

    Observe que o botão está habilitado — embora ainda não faça nada útil.

  4. Excluir o valor Name

    Isso viola a regra "obrigatória", então exibe a mensagem de erro — e observe que também desabilita o botão Submit.

    Você não precisou conectar explicitamente o estado habilitado do botão à validade do formulário. O FormsModule fez isso automaticamente quando você definiu uma variável de referência de template no elemento de formulário aprimorado, depois referenciou essa variável no controle do botão.

Responder ao envio do formulário

Para mostrar uma resposta ao envio do formulário, você pode ocultar a área de entrada de dados e exibir outra coisa em seu lugar.

  1. Encapsular o formulário

    Envolva todo o formulário em um <div> e vincule sua propriedade hidden à propriedade ActorFormComponent.submitted.

    actor-form.component.html (excerpt)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

    O formulário principal está visível desde o início porque a propriedade submitted é false até que você envie o formulário, como este fragmento do ActorFormComponent mostra:

    actor-form.component.ts (submitted)

    import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}

    Quando você clica no botão Submit, o flag submitted se torna true e o formulário desaparece.

  2. Adicionar o estado submitted

    Para mostrar outra coisa enquanto o formulário está no estado submitted, adicione o seguinte HTML abaixo do novo wrapper <div>.

    actor-form.component.html (excerpt)

    <div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

    Este <div>, que mostra um actor somente leitura com vinculações de interpolação, aparece apenas enquanto o component está no estado submitted.

    A exibição alternativa inclui um botão Edit cujo evento de clique está vinculado a uma expressão que limpa o flag submitted.

  3. Testar o botão Edit

    Clique no botão Edit para alternar a exibição de volta para o formulário editável.

Resumo

O formulário Angular discutido nesta página aproveita os seguintes recursos do framework para fornecer suporte para modificação de dados, validação e muito mais.

  • Um template de formulário HTML do Angular
  • Uma classe component de formulário com um decorator @Component
  • Manipulação de envio de formulário vinculando à propriedade de evento NgForm.ngSubmit
  • Variáveis de referência de template como #actorForm e #name
  • Sintaxe [(ngModel)] para vinculação de dados bidirecional
  • O uso de atributos name para validação e rastreamento de alteração de elemento de formulário
  • A propriedade valid da variável de referência em controles de entrada indica se um controle é válido ou deve mostrar mensagens de erro
  • Controlar o estado habilitado do botão Submit vinculando à validade do NgForm
  • Classes CSS personalizadas que fornecem feedback visual aos usuários sobre controles que não são válidos

Aqui está o código para a versão final do aplicativo:

actor-form.component.ts

import {Component} from '@angular/core';import {Actor} from '../actor';import {FormsModule} from '@angular/forms';import {JsonPipe} from '@angular/common';@Component({  selector: 'app-actor-form',  templateUrl: './actor-form.component.html',  imports: [FormsModule, JsonPipe],})export class ActorFormComponent {  skills = ['Method Acting', 'Singing', 'Dancing', 'Swordfighting'];  model = new Actor(18, 'Tom Cruise', this.skills[3], 'CW Productions');  submitted = false;  onSubmit() {    this.submitted = true;  }  newActor() {    this.model = new Actor(42, '', '');  }  heroine(): Actor {    const myActress = new Actor(42, 'Marilyn Monroe', 'Singing');    console.log('My actress is called ' + myActress.name); // "My actress is called Marilyn"    return myActress;  }  //////// NOT SHOWN IN DOCS ////////  // Reveal in html:  //   Name via form.controls = {{showFormControls(actorForm)}}  showFormControls(form: any) {    return form && form.controls.name && form.controls.name.value; // Tom Cruise  }  /////////////////////////////}

actor-form.component.html

<div class="container">  <div [hidden]="submitted">    <h1>Actor Form</h1>    <form (ngSubmit)="onSubmit()" #actorForm="ngForm">      <div class="form-group">        <label for="name">Name</label>        <input type="text" class="form-control" id="name"               required [(ngModel)]="model.name" name="name"               #name="ngModel">        <div [hidden]="name.valid || name.pristine"             class="alert alert-danger">          Name is required        </div>      </div>      <div class="form-group">        <label for="studio">Studio Affiliation</label>        <input type="text" class="form-control" id="studio"               [(ngModel)]="model.studio" name="studio">      </div>      <div class="form-group">        <label for="skill">Skill</label>        <select class="form-control" id="skill"                required [(ngModel)]="model.skill" name="skill"                #skill="ngModel">          @for (skill of skills; track $index) {            <option [value]="skill">{{ skill }}</option>          }        </select>        <div [hidden]="skill.valid || skill.pristine" class="alert alert-danger">          skill is required        </div>      </div>      <button type="submit" class="btn btn-success"        [disabled]="!actorForm.form.valid">Submit</button>      <button type="button" class="btn btn-default"        (click)="newActor(); actorForm.reset()">New Actor</button>      <em>with</em> reset      &nbsp;&nbsp;      <button type="button" class="btn btn-default"        (click)="newActor()">New Actor</button>      <em>without</em> reset     <!-- NOT SHOWN IN DOCS -->      <div>        <hr>        Name via form.controls = {{ showFormControls(actorForm) }}      </div>     <!-- - -->    </form>  </div>  <div [hidden]="!submitted">    <h2>You submitted the following:</h2>    <div class="row">      <div class="col-xs-3">Name</div>      <div class="col-xs-9">{{ model.name }}</div>    </div>    <div class="row">      <div class="col-xs-3">Studio</div>      <div class="col-xs-9">{{ model.studio }}</div>    </div>    <div class="row">      <div class="col-xs-3">Skill</div>      <div class="col-xs-9">{{ model.skill }}</div>    </div>    <br>    <button type="button" class="btn btn-primary" (click)="submitted=false">      Edit    </button>  </div></div><!-- ====================================================  -->  <div>    <form>       <!-- ... all of the form ... -->    </form>  </div><!-- ====================================================  --><hr><style>  .no-style .ng-valid {  border-left: 1px  solid #CCC}  .no-style .ng-invalid {  border-left: 1px  solid #CCC}</style><div class="no-style" style="margin-left: 4px">  <div class="container">      <h1>Actor Form</h1>      <form>        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name" required>        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text" class="form-control" id="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control" id="skill" required>            @for(skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- ====================================================  -->  <hr>  <div class="container">      <h1>Actor Form</h1>      <form #actorForm="ngForm">        {{ model | json }}        <div class="form-group">          <label for="name">Name</label>          <input type="text" class="form-control" id="name"                 required                 [(ngModel)]="model.name" name="name">        </div>        <div class="form-group">          <label for="studio">Studio</label>          <input type="text"  class="form-control" id="studio"                 [(ngModel)]="model.studio" name="studio">        </div>        <div class="form-group">          <label for="skill">Skill</label>          <select class="form-control"  id="skill"                  required                  [(ngModel)]="model.skill" name="skill">            @for (skill of skills; track $index) {              <option [value]="skill">{{ skill }}</option>            }          </select>        </div>        <button type="submit" class="btn btn-success">Submit</button>      </form>  </div>  <!-- EXTRA MATERIAL FOR DOCUMENTATION -->  <hr>    <input type="text" class="form-control" id="name"           required           [(ngModel)]="model.name" name="name">    TODO: remove this: {{ model.name}}  <hr>    <input type="text" class="form-control" id="name"           required           [ngModel]="model.name" name="name"           (ngModelChange)="model.name = $event">    TODO: remove this: {{ model.name}}</div>

actor.ts

export class Actor {  constructor(    public id: number,    public name: string,    public skill: string,    public studio?: string,  ) {}}

app.component.html

<app-actor-form />

app.component.ts

import {Component} from '@angular/core';import {ActorFormComponent} from './actor-form/actor-form.component';@Component({  selector: 'app-root',  templateUrl: './app.component.html',  imports: [ActorFormComponent],})export class AppComponent {}

main.ts

import {bootstrapApplication} from '@angular/platform-browser';import {AppComponent} from './app/app.component';import {provideZoneChangeDetection} from '@angular/core';bootstrapApplication(AppComponent, {  providers: [provideZoneChangeDetection({eventCoalescing: true})],}).catch((err) => console.error(err));

forms.css

.ng-valid[required], .ng-valid.required  {  border-left: 5px solid #42A948; /* green */}.ng-invalid:not(form)  {  border-left: 5px solid #a94442; /* red */}