Guías Detalladas
Formularios

Construyendo un formulario basado en plantillas

Este tutorial te muestra cómo crear un formulario basado en plantillas. Los elementos de control en el formulario están vinculados a propiedades de datos que tienen validación de entrada. La validación de entrada ayuda a mantener la integridad de los datos y el estilo, lo que mejora la experiencia del usuario.

Los formularios basados en plantillas usan enlace de datos bidireccional para actualizar el modelo de datos en el componente a medida que se realizan cambios en la plantilla y viceversa.

Formularios basados en plantillas vs Formularios reactivos

Angular soporta dos enfoques de diseño para formularios interactivos. Los formularios basados en plantillas te permiten usar directivas específicas de formulario en tu plantilla de Angular. Los formularios reactivos proporcionan un enfoque basado en modelos para construir formularios.

Los formularios basados en plantillas son una excelente opción para formularios pequeños o simples, mientras que los formularios reactivos son más escalables y adecuados para formularios complejos. Para una comparación de los dos enfoques, consulta Elegir un enfoque

Puedes construir casi cualquier tipo de formulario con una plantilla de Angular formularios de inicio de sesión, formularios de contacto, y prácticamente cualquier formulario de negocio. Puedes diseñar los controles de forma creativa y vincularlos a los datos en tu modelo de objeto. Puedes especificar reglas de validación, mostrar errores de validación, permitir condicionalmente entrada en controles específicos, activar retroalimentación visual integrada, y mucho más.

Objetivos

Este tutorial te enseña cómo hacer lo siguiente:

  • Construir un formulario de Angular con un componente y plantilla
  • Usar ngModel para crear enlaces de datos bidireccionales para leer y escribir valores de control de entrada
  • Proporcionar retroalimentación visual usando clases CSS especiales que rastrean el estado de los controles
  • Mostrar errores de validación a los usuarios y permitir la entrada de datos en los controles del formulario de forma condicional, basándose en el estado del mismo.
  • Compartir información entre elementos HTML usando variables de referencia de plantilla

Construir un formulario basado en plantillas

Los formularios basados en plantillas se basan en directivas definidas en el FormsModule.

Directives Details
NgModel Reconcilia los cambios de valor en el elemento de formulario adjunto con los cambios en el modelo de datos, lo que te permite responder a la entrada del usuario con validación y manejo de errores.
NgForm Crea una instancia de FormGroup de nivel superior y la vincula a un elemento <form> para rastrear el valor agregado del formulario y el estado de validación. Tan pronto como importas FormsModule, esta directiva se vuelve activa por defecto en todas las etiquetas <form>. No necesitas agregar un selector especial.
NgModelGroup Crea y vincula una instancia de FormGroup a un elemento DOM.

Resumen de los pasos

En el curso de este tutorial, vinculas un formulario de muestra a datos y manejas la entrada del usuario usando los siguientes pasos.

  1. Construir el formulario básico.
    • Definir un modelo de datos de muestra
    • Incluir infraestructura requerida como el FormsModule
  2. Vincular controles de formulario a propiedades de datos usando la directiva ngModel y la sintaxis de enlace de datos bidireccional.
    • Examinar cómo ngModel reporta estados de control usando clases CSS
    • Nombrar controles para hacerlos accesibles a ngModel
  3. Rastrear validez de entrada y estado de control usando ngModel.
    • Agregar CSS personalizado para proporcionar retroalimentación visual sobre el estado
    • Mostrar y ocultar mensajes de error de validación
  4. Responder a un evento de clic de botón HTML nativo agregando al modelo de datos.
  5. Manejar el envío del formulario usando la propiedad de salida ngSubmit del formulario.
    • Deshabilitar el botón Submit hasta que el formulario sea válido
    • Después del envío, intercambiar el formulario terminado por contenido diferente en la página

Construir el formulario

  1. La aplicación de ejemplo proporcionada crea la clase Actor que define el modelo de datos reflejado en el formulario.

src/app/actor.ts

export class Actor {  constructor(    public id: number,    public name: string,    public skill: string,    public studio?: string,  ) {}}
  1. El diseño y detalles del formulario están definidos en la clase ActorFormComponent.

    src/app/actor-form/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  }  /////////////////////////////}

    El valor selector del componente de "app-actor-form" significa que puedes colocar este formulario en una plantilla padre usando la etiqueta <app-actor-form>.

  2. El siguiente código crea una nueva instancia de actor, para que el formulario inicial pueda mostrar un actor de ejemplo.

    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 demo usa datos ficticios para model y skills. En una aplicación real, inyectarías un servicio de datos para obtener y guardar datos reales, o exponerías estas propiedades como entradas y salidas.

  3. El siguiente código crea una nueva instancia de actor, para que el formulario inicial pueda mostrar un actor de ejemplo.

    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. El formulario se muestra en el diseño de la aplicación definido por la plantilla del componente raíz.

    src/app/app.component.html

    <app-actor-form />

    La plantilla inicial define el diseño para un formulario con dos grupos de formulario y un botón de envío. Los grupos de formulario corresponden a dos propiedades del modelo de datos Actor, nombre y estudio. Cada grupo tiene una etiqueta y una caja para entrada de datos del usuario.

    • El elemento de control Name <input> tiene el atributo HTML5 required
    • El elemento de control Studio <input> no lo tiene porque studio es opcional

    El botón Submit tiene algunas clases para estilos. En este punto, el diseño del formulario es todo HTML5 plano, sin enlaces o directivas.

  5. El formulario de muestra usa algunas clases de estilo de Twitter Bootstrap: container, form-group, form-control, y btn. Para usar estos estilos, la hoja de estilo de la aplicación importa la biblioteca.

src/styles.css

@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
  1. El formulario requiere que la habilidad de un actor se elija de una lista predefinida de skills mantenida internamente en ActorFormComponent. El bucle Angular @for itera sobre los valores de datos para llenar el elemento <select>.

src/app/actor-form/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              <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>

Si ejecutas la aplicación, verás la lista de habilidades en el control de selección. Los elementos de entrada aún no están enlazados a valores de datos o eventos, por lo que todavía están en blanco y no tienen comportamiento.

Vincular controles de entrada a propiedades de datos

El siguiente paso es vincular los controles de entrada a las propiedades Actor correspondientes con el enlace de datos bidireccional, para que respondan a la entrada del usuario actualizando el modelo de datos, y también respondan a los cambios programáticos en los datos actualizando la visualización.

La directiva ngModel declarada en FormsModule te permite vincular controles en tu formulario basado en plantillas a propiedades en tu modelo de datos. Cuando incluyes la directiva usando la sintaxis para enlace de datos bidireccional, [(ngModel)], Angular puede rastrear el valor y la interacción del usuario del control y mantener la vista sincronizada con el modelo.

  1. Edita el archivo de plantilla actor-form.component.html.
  2. Encuentra la etiqueta <input> junto a la etiqueta Name.
  3. Agrega la directiva ngModel, usando la sintaxis de enlace de datos bidireccional [(ngModel)]="...".

src/app/actor-form/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              <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>

ÚTIL: Este ejemplo tiene una interpolación de diagnóstico temporal después de cada etiqueta de entrada, {{model.name}}, para mostrar el valor de datos actual de la propiedad correspondiente. El comentario te recuerda eliminar las líneas de diagnóstico cuando hayas terminado de observar el enlace de datos bidireccional en funcionamiento.

Acceder al estado general del formulario

Cuando importaste FormsModule en tu componente, Angular automáticamente creó y adjuntó una directiva NgForm a la etiqueta <form> en la plantilla (porque NgForm tiene el selector form que coincide con elementos <form>).

Para obtener acceso a NgForm y el estado general del formulario, declara una variable de referencia de plantilla.

  1. Edita el archivo de plantilla actor-form.component.html.
  2. Actualiza la etiqueta <form> con una variable de referencia de plantilla, #actorForm, y establece su valor como sigue.

    src/app/actor-form/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              <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>

La variable de plantilla actorForm ahora es una referencia a la instancia de directiva NgForm que gobierna el formulario como un todo.

  1. Ejecuta la aplicación.

  2. Comienza a escribir en la caja de entrada Name.

    A medida que agregas y eliminas caracteres, puedes verlos aparecer y desaparecer del modelo de datos.

La línea de diagnóstico que muestra valores interpolados demuestra que los valores fluyen de la caja de entrada al modelo y viceversa.

Nombrando elementos de control

Cuando usas [(ngModel)] en un elemento, debes definir un atributo name para ese elemento. Angular usa el nombre asignado para registrar el elemento con la directiva NgForm adjunta al elemento <form> padre.

El ejemplo agregó un atributo name al elemento <input> y lo estableció como "name", lo cual tiene sentido para el nombre del actor. Cualquier valor único funcionará, pero es útil usar un nombre descriptivo.

  1. Agrega enlaces [(ngModel)] similares y atributos name a Studio y Skill.
  2. Ahora puedes eliminar los mensajes de diagnóstico que muestran valores interpolados.
  3. Para confirmar que el enlace de datos bidireccional funciona para todo el modelo de actor, agrega un nuevo enlace de texto con el pipe json en la parte superior de la plantilla del componente, que serializa los datos a una cadena.

Después de estas revisiones, la plantilla del formulario debería verse como así:

src/app/actor-form/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              <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>

Notarás que:

  • Cada elemento <input> tiene una propiedad id. Esto es utilizado por el atributo for del elemento <label> para hacer coincidir la etiqueta con su control de entrada. Esta es una característica estándar de HTML.

  • Cada elemento <input> también tiene la propiedad name requerida que Angular usa para registrar el control con el formulario..

Una vez que hayas observado los efectos, puedes eliminar el enlace de texto {{ model | json }}.

Rastrear los estados del formulario

Angular aplica la clase ng-submitted a elementos form después de que el formulario ha sido enviado. Estas clases pueden usarse para cambiar el estilo del formulario después de que ha sido enviado

Rastrear los estados de control

Agregar la directiva NgModel a un control agrega nombres de clase al control que describen su estado.

Estas clases pueden usarse para cambiar el estilo de un control basado en su estado.

Estados Clase si es verdadero Clase si es falso
El control ha sido visitado. ng-touched ng-untouched
El valor del control ha cambiado. ng-dirty ng-pristine
El valor del control es válido. ng-valid ng-invalid

Angular también aplica la clase ng-submitted a los elementos form al enviar, pero no a los controles dentro del elemento form.

Puedes usar estas clases CSS para definir los estilos de tu control en función de su estado.

Observar los estados del control

Para ver cómo las clases son agregadas y eliminadas por el framework, abre las herramientas de desarrollador del navegador e inspecciona el elemento <input> que representa el nombre del actor.

Usando las herramientas de desarrollador de tu navegador, encuentra el elemento <input> que corresponde a la caja de entrada Name. You can see that the element has multiple CSS classes in addition to "form-control".

  1. Cuando lo abres por primera vez, las clases indican que tiene un valor válido, que el valor no ha sido cambiado desde la inicialización o reset, y que el control no ha sido visitado desde la inicialización o reset.

    <input class="form-control ng-untouched ng-pristine ng-valid">;
  2. Realiza las siguientes acciones en la caja de entrada <input> Name, y observa qué clases aparecen.

    • Observa sin tocar. Las clases indican que está sin tocar, pristino y válido.

    • Haz clic dentro de la caja de nombre, luego haz clic fuera de ella. El control ahora ha sido visitado, y el elemento tiene la clase ng-touched en lugar de la clase ng-untouched.

    • Agrega barras al final del nombre. Ahora está en estado touched y dirty.

    • Borra el nombre. Esto hace que el valor sea inválido, por lo que la clase ng-invalid reemplaza la clase ng-valid.

Crea retroalimentación visual para los estados

El par ng-valid/ng-invalid es particularmente interesante, porque quieres enviar una señal visual fuerte cuando los valores son inválidos. También quieres marcar campos requeridos.

Puedes marcar campos requeridos y datos inválidos al mismo tiempo con una barra coloreada a la izquierda de la caja de entrada.

Para cambiar la apariencia de esta manera, sigue estos pasos.

  1. Agrega definiciones para las clases CSS ng-*.
  2. Agrega estas definiciones de clase a un nuevo archivo forms.css.
  3. Agrega el nuevo archivo al proyecto como hermano 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. In the index.html file, update the <head> tag to include the new style sheet.

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 y ocultar mensajes de error de validación

La caja de entrada Name es requerida y limpiarla convierte la barra en roja. Eso indica que algo está mal, pero el usuario no sabe qué está mal o qué hacer al respecto. Puedes proporcionar un mensaje útil verificando y respondiendo al estado del control.

El control de de selección Skill también es requerida, pero no necesita este tipo de manejo de errores porque ya restringe la selección a valores válidos.

Para definir y mostrar un mensaje de error cuando sea apropiado, sigue estos pasos.

  1. Agregar una referencia local a la entrada

    Extiende la etiqueta input con una variable de referencia de plantilla que puedes usar para acceder al control Angular de la caja de entrada desde dentro de la plantilla. En el ejemplo, la variable es #name="ngModel".

    La variable de referencia de plantilla (#name) se establece como "ngModel" porque ese es el valor de la propiedad NgModel.exportAs. Esta propiedad le dice a Angular cómo vincular una variable de referencia a una directiva.

  2. Agregar el mensaje de error

    Agrega un <div> que contenga un mensaje de error apropiado.

  3. Hacer el mensaje de error condicional

    Muestra u oculta el mensaje de error vinculando propiedades del control name a la propiedad hidden del elemento <div> del mensaje.

  4. src/app/actor-form/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              <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. Agregar un mensaje de error condicional al nombre

    Agrega un mensaje de error condicional a la caja de entrada name, como en el siguiente ejemplo.

    src/app/actor-form/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              <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>

En este ejemplo, ocultas el mensaje cuando el control es válido o pristine. Pristine significa que el usuario no ha cambiado el valor desde que se mostró en este formulario. Si ignoras el estado pristine, ocultarías el mensaje solo cuando el valor es válido. Si llegas a este componente con un actor nuevo y en blanco o un actor inválido, verás el mensaje de error inmediatamente, antes de haber hecho algo.

Podrías querer que el mensaje se muestre solo cuando el usuario hace un cambio inválido. Ocultar el mensaje mientras el control está en el estado pristine logra ese objetivo. Verás la importancia de esta elección cuando agregues un nuevo actor al formulario en el siguiente paso.

Agrega un nuevo actor

Este ejercicio muestra cómo puedes responder a un evento de clic de botón HTML nativo agregando al modelo de datos. Para permitir que los usuarios del formulario agreguen un nuevo actor, agregarás un botón New Actor que responde a un evento de clic.

  1. En la plantilla, coloca un elemento <button> "New Actor" en la parte inferior del formulario.
  2. En el archivo del componente, agrega el método de creación de actor al modelo de datos de actor.

src/app/actor-form/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. Vincula el evento de clic del botón a un método de creación de actor, newActor().

src/app/actor-form/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              <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. Ejecuta la aplicación nuevamente y haz clic en el botón New Actor.

    El formulario se limpia, y las barras required a la izquierda de la caja de entrada se vuelven rojas, indicando propiedades name y skill inválidas. Observa que los mensajes de error están ocultos. Esto es porque el formulario es pristino; no has cambiado nada aún.

  2. Ingresa un nombre y haz clic en New Actor nuevamente.

    Ahora la aplicación muestra un mensaje de error Name is required, porque la caja de entrada ya no está en estado pristine. El formulario recuerda que ingresaste un nombre antes de hacer clic en New Actor.

  3. Para restaurar el estado pristino de los controles del formulario, limpia todas las banderas imperativamente llamando al método reset() del formulario después de llamar al método newActor().

    src/app/actor-form/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              <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>

    Ahora hacer clic en New Actor resetea tanto el formulario como sus banderas de control.

Enviar el formulario con ngSubmit

El usuario debería poder enviar este formulario después de llenarlo. El botón Submit en la parte inferior del formulario no hace nada por sí solo, pero activa un evento de envío de formulario debido a su tipo (type="submit").

Para responder a este evento, sigue los siguientes pasos.

  1. Escuchar ngOnSubmit

    Vincula la propiedad de evento ngSubmit del formulario al método onSubmit() del componente actor-form.

    src/app/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              <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 la propiedad disabled

    Usa la variable de referencia de plantilla, #actorForm para acceder al formulario que contiene el botón Submit y crear un enlace de evento.

    Vincularás la propiedad del formulario que indica su validez general a la propiedad disabled del botón Submit.

    src/app/actor-form/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              <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. Ejecutar la aplicación

    Observa que el botón está habilitado —aunque aún no hace nada útil.

  4. Eliminar el valor Name

    Esto viola la regla "required", así que muestra el mensaje de error —y observa que también deshabilita el botón Submit.

    No tuviste que conectar explícitamente el estado habilitado del botón a la validez del formulario. FormsModule hizo esto automáticamente cuando definiste una variable de referencia de plantilla en el elemento de formulario mejorado, luego referiste a esa variable en el control del botón.

Responder al envío del formulario

Para mostrar una respuesta al envío del formulario, puedes ocultar el área de entrada de datos y mostrar algo más en su lugar.

  1. Envolver el formulario

    Envuelve todo el formulario en un <div> y vincula su propiedad hidden a la propiedad ActorFormComponent.submitted.

    src/app/actor-form/actor-form.component.html (extracto)

    <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              <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>

    El formulario principal es visible desde el inicio porque la propiedad submitted es falsa hasta que envías el formulario, como muestra este fragmento de ActorFormComponent:

    src/app/actor-form/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  }  /////////////////////////////}

    Cuando haces clic en el botón Submit, la bandera submitted se vuelve verdadera y el formulario desaparece.

  2. Agregar el estado enviado

    Para mostrar algo más mientras el formulario está en el estado enviado, agrega el siguiente HTML debajo del nuevo envoltorio <div>.

    src/app/actor-form/actor-form.component.html (extracto)

    <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              <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 muestra un actor de solo lectura con enlaces de interpolación, aparece solo mientras el componente está en el estado enviado.

    La visualización alternativa incluye un botón Edit cuyo evento de clic está vinculado a una expresión que limpia la bandera submitted.

  3. Probar el botón Edit

    Haz clic en el botón Edit para cambiar la visualización de vuelta al formulario editable.

Resumen

El formulario de Angular discutido en esta página aprovecha las siguientes características del framework para proporcionar soporte para modificación de datos, validación y más.

  • Una plantilla de formulario HTML de Angular
  • Una clase de componente de formulario que utiliza un decorador @Component
  • Manejo del envío del formulario vinculando a la propiedad de evento NgForm.ngSubmit
  • Variables de referencia de plantilla como #actorForm y #name
  • Sintaxis [(ngModel)] para enlace de datos bidireccional
  • El uso de atributos name para la validación y el seguimiento de lo scambios en los elementos del formulario
  • La propiedad valid de la variable de referencia en controles de entrada indica si un control es válido o debería mostrar mensajes de error
  • Controlar el estado habilitado del botón Submit vinculando a la validez de NgForm
  • Clases CSS personalizadas que proporcionan retroalimentación visual a los usuarios sobre controles que no son válidos

Aquí está el código para la versión final de la aplicación:

actor-form/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/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              <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 */}