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.
- Construir el formulario básico.
- Definir un modelo de datos de muestra
- Incluir infraestructura requerida como el
FormsModule
- 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
- Examinar cómo
- 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
- Responder a un evento de clic de botón HTML nativo agregando al modelo de datos.
- 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
- 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, ) {}}
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>
.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
yskills
. 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.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 } /////////////////////////////}
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 HTML5required
- El elemento de control Studio
<input>
no lo tiene porquestudio
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.
- El elemento de control Name
El formulario de muestra usa algunas clases de estilo de Twitter Bootstrap:
container
,form-group
,form-control
, ybtn
. 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');
- El formulario requiere que la habilidad de un actor se elija de una lista predefinida de
skills
mantenida internamente enActorFormComponent
. 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.
- Edita el archivo de plantilla
actor-form.component.html
. - Encuentra la etiqueta
<input>
junto a la etiqueta Name. - 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.
- Edita el archivo de plantilla
actor-form.component.html
. - 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.
Ejecuta la aplicación.
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.
- Agrega enlaces
[(ngModel)]
similares y atributosname
a Studio y Skill. - Ahora puedes eliminar los mensajes de diagnóstico que muestran valores interpolados.
- 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 propiedadid
. Esto es utilizado por el atributofor
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 propiedadname
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".
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">;
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 claseng-untouched
.Agrega barras al final del nombre. Ahora está en estado
touched
ydirty
.Borra el nombre. Esto hace que el valor sea inválido, por lo que la clase
ng-invalid
reemplaza la claseng-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.
- Agrega definiciones para las clases CSS
ng-*
. - Agrega estas definiciones de clase a un nuevo archivo
forms.css
. - 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 */}
- 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.
-
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 propiedadNgModel.exportAs
. Esta propiedad le dice a Angular cómo vincular una variable de referencia a una directiva. -
Agregar el mensaje de error
Agrega un
<div>
que contenga un mensaje de error apropiado. -
Hacer el mensaje de error condicional
Muestra u oculta el mensaje de error vinculando propiedades del control
name
a la propiedadhidden
del elemento<div>
del mensaje. -
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>
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>
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.
- En la plantilla, coloca un elemento
<button>
"New Actor" en la parte inferior del formulario. - 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 } /////////////////////////////}
- 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>
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
yskill
inválidas. Observa que los mensajes de error están ocultos. Esto es porque el formulario es pristino; no has cambiado nada aún.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 estadopristine
. El formulario recuerda que ingresaste un nombre antes de hacer clic en New Actor.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étodonewActor()
.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.
-
Escuchar ngOnSubmit
Vincula la propiedad de evento
ngSubmit
del formulario al métodoonSubmit()
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>
-
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>
-
Ejecutar la aplicación
Observa que el botón está habilitado —aunque aún no hace nada útil.
-
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.
-
Envolver el formulario
Envuelve todo el formulario en un
<div>
y vincula su propiedadhidden
a la propiedadActorFormComponent.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 deActorFormComponent
: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. -
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
. -
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 */}