From 382aa50c54e581fa74e0b6324c7103e4b894a1fc Mon Sep 17 00:00:00 2001 From: Judy Bogart Date: Wed, 8 Apr 2020 14:08:04 -0700 Subject: [PATCH] docs: refactor template-driven forms doc as a tutorial (#36732) rework content to meet current documentation standards and conventions, structure as tutorial document type PR Close #36732 --- .../examples/forms/src/app/app.module.ts | 2 +- .../app/hero-form/hero-form.component.html | 9 - .../src/app/hero-form/hero-form.component.ts | 2 +- aio/content/guide/forms-overview.md | 11 +- aio/content/guide/forms.md | 667 +++++++----------- .../forms/control-state-transitions-anim.gif | Bin 226076 -> 0 bytes .../guide/forms/ng-control-class-changes.png | Bin 41737 -> 0 bytes aio/content/navigation.json | 10 +- 8 files changed, 251 insertions(+), 450 deletions(-) delete mode 100644 aio/content/images/guide/forms/control-state-transitions-anim.gif delete mode 100644 aio/content/images/guide/forms/ng-control-class-changes.png diff --git a/aio/content/examples/forms/src/app/app.module.ts b/aio/content/examples/forms/src/app/app.module.ts index 51b9b9afe24224..94d11f726e4e68 100644 --- a/aio/content/examples/forms/src/app/app.module.ts +++ b/aio/content/examples/forms/src/app/app.module.ts @@ -3,7 +3,7 @@ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; -import { AppComponent } from './app.component'; +import { AppComponent } from './app.component'; import { HeroFormComponent } from './hero-form/hero-form.component'; @NgModule({ diff --git a/aio/content/examples/forms/src/app/hero-form/hero-form.component.html b/aio/content/examples/forms/src/app/hero-form/hero-form.component.html index 0a270bc7f0dc07..dbbc1c3908ccf1 100644 --- a/aio/content/examples/forms/src/app/hero-form/hero-form.component.html +++ b/aio/content/examples/forms/src/app/hero-form/hero-form.component.html @@ -200,13 +200,4 @@

Hero Form

(ngModelChange)="model.name = $event"> TODO: remove this: {{model.name}} -
- - -
TODO: remove this: {{spy.className}} - - diff --git a/aio/content/examples/forms/src/app/hero-form/hero-form.component.ts b/aio/content/examples/forms/src/app/hero-form/hero-form.component.ts index bc5fe12ef81ba6..19f83e7d9637ca 100644 --- a/aio/content/examples/forms/src/app/hero-form/hero-form.component.ts +++ b/aio/content/examples/forms/src/app/hero-form/hero-form.component.ts @@ -2,7 +2,7 @@ // #docregion , v1, final import { Component } from '@angular/core'; -import { Hero } from '../hero'; +import { Hero } from '../hero'; @Component({ selector: 'app-hero-form', diff --git a/aio/content/guide/forms-overview.md b/aio/content/guide/forms-overview.md index aca51c47f35e46..1828c703488c6f 100644 --- a/aio/content/guide/forms-overview.md +++ b/aio/content/guide/forms-overview.md @@ -279,11 +279,12 @@ Here are the steps performed in the model to view test. To learn more about reactive forms, see the following guides: -* [Reactive forms](guide/reactive-forms) guide -* [Form validation](guide/form-validation#reactive-form-validation) guide -* [Building dynamic forms](guide/dynamic-form) tutorial +* [Reactive forms](guide/reactive-forms) +* [Form validation](guide/form-validation#reactive-form-validation) +* [Dynamic forms](guide/dynamic-form) To learn more about template-driven forms, see the following guides: -* [Building a template-driven form](guide/forms#template-driven-forms) tutorial -* [Form validation](guide/form-validation#template-driven-validation) guide +* [Building a template-driven form](guide/forms) tutorial +* [Form validation](guide/form-validation#template-driven-validation) +* `NgForm` directive API reference diff --git a/aio/content/guide/forms.md b/aio/content/guide/forms.md index e7bd8ea30b28e5..ad00df810c00dc 100644 --- a/aio/content/guide/forms.md +++ b/aio/content/guide/forms.md @@ -1,389 +1,234 @@ -# Template-driven forms - -Forms are the mainstay of business applications. -You use forms to log in, submit a help request, place an order, book a flight, -schedule a meeting, and perform countless other data-entry tasks. - -In developing a form, it's important to create a data-entry experience that guides the -user efficiently and effectively through the workflow. - -
- - For the sample app that this page describes, see the . - -
- -## Introduction to Template-driven forms - -Developing forms requires design skills (which are out of scope for this page), as well as framework support for -*two-way data binding, change tracking, validation, and error handling*, -which you'll learn about on this page. - -This page shows you how to build a simple form from scratch. Along the way you'll learn how to: - -* Build an Angular form with a component and template. -* Use `ngModel` to create two-way data bindings for reading and writing input-control values. -* Track state changes and the validity of form controls. -* Provide visual feedback using special CSS classes that track the state of the controls. -* Display validation errors to users and enable/disable form controls. -* Share information across HTML elements using template reference variables. +# Building a template-driven form {@a template-driven} -You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with -the form-specific directives and techniques described in this page. - -
+This tutorial shows you how to create a template-driven form whose control elements are bound to data properties, with input validation to maintain data integrity and styling to improve the user experience. - You can also use a reactive (or model-driven) approach to build forms. - However, this page focuses on template-driven forms. +Template-driven forms use [two-way data binding](guide/architecture-components#data-binding "Intro to 2-way data binding") to update the data model in the component as changes are made in the template and vice versa. -
- -You can build almost any form with an Angular template—login forms, contact forms, and pretty much any business form. -You can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, -conditionally enable or disable specific controls, trigger built-in visual feedback, and much more. +
-Angular makes the process easy by handling many of the repetitive, boilerplate tasks you'd -otherwise wrestle with yourself. +Angular supports two design approaches for interactive forms. You can build forms by writing templates using Angular [template syntax and directives](guide/glossary#template "Definition of template terms") with the form-specific directives and techniques described in this tutorial, or you can use a reactive (or model-driven) approach to build forms. -You'll learn to build a template-driven form that looks like this: +Template-driven forms are suitable for small or simple forms, while reactive forms are more scalable and suitable for complex forms. +For a comparison of the two approaches, see [Introduction to Forms](guide/forms-overview "Overview of Angular forms.") - -The *Hero Employment Agency* uses this form to maintain personal information about heroes. -Every hero needs a job. It's the company mission to match the right hero with the right crisis. - -Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot. - -If you delete the hero name, the form displays a validation error in an attention-grabbing style: - - +You can build almost any kind of form with an Angular template—login forms, contact forms, and pretty much any business form. +You can lay out the controls creatively and bind them to the data in your object model. +You can specify validation rules and display validation errors, +conditionally enable or disable specific controls, trigger built-in visual feedback, and much more. -Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red. +This tutorial shows you how to build a form from scratch, using a simplified sample form like the one from the [Tour of Heroes tutorial](tutorial "Tour of Heroes") to illustrate the techniques.
- You can customize the colors and location of the "required" bar with standard CSS. + Run or download the example app: .
-You'll build this form in small steps: - -1. Create the `Hero` model class. -1. Create the component that controls the form. -1. Create a template with the initial form layout. -1. Bind data properties to each form control using the `ngModel` two-way data-binding syntax. -1. Add a `name` attribute to each form-input control. -1. Add custom CSS to provide visual feedback. -1. Show and hide validation-error messages. -1. Handle form submission with *ngSubmit*. -1. Disable the form’s *Submit* button until the form is valid. - -## Setup +## Objectives -Create a new project named angular-forms: +This tutorial teaches you how to do the following: - - - ng new angular-forms - - - -## Create the Hero model class - -As users enter form data, you'll capture their changes and update an instance of a model. -You can't lay out the form until you know what the model looks like. - -A model can be as simple as a "property bag" that holds facts about a thing of application importance. -That describes well the `Hero` class with its three required fields (`id`, `name`, `power`) -and one optional field (`alterEgo`). - -Using the Angular CLI command [`ng generate class`](cli/generate), generate a new class named `Hero`: - - - - ng generate class Hero - - - -With this content: - - - -It's an anemic model with few requirements and no behavior. Perfect for the demo. - -The TypeScript compiler generates a public field for each `public` constructor parameter and -automatically assigns the parameter’s value to that field when you create heroes. - -The `alterEgo` is optional, so the constructor lets you omit it; note the question mark (?) in `alterEgo?`. - -You can create a new hero like this: - - - -## Create a form component - -An Angular form has two parts: an HTML-based _template_ and a component _class_ -to handle data and user interactions programmatically. -Begin with the class because it states, in brief, what the hero editor can do. - -Using the Angular CLI command [`ng generate component`](cli/generate), generate a new component named `HeroForm`: - - - - ng generate component HeroForm - - - -With this content: - - - -There’s nothing special about this component, nothing form-specific, -nothing to distinguish it from any component you've written before. - -Understanding this component requires only the Angular concepts covered in previous pages. - -* The code imports the Angular core library and the `Hero` model you just created. -* The `@Component` selector value of "app-hero-form" means you can drop this form in a parent -template with a `` tag. -* The `templateUrl` property points to a separate file for the template HTML. -* You defined dummy data for `model` and `powers`, as befits a demo. - -Down the road, you can inject a data service to get and save real data -or perhaps expose these properties as inputs and outputs -(see [Input and output properties](guide/template-syntax#inputs-outputs) on the -[Template Syntax](guide/template-syntax) page) for binding to a -parent component. This is not a concern now and these future changes won't affect the form. - -* You added a `diagnostic` property to return a JSON representation of the model. -It'll help you see what you're doing during development; you've left yourself a cleanup note to discard it later. - -## Revise *app.module.ts* +* Build an Angular form with a component and template. +* Use `ngModel` to create two-way data bindings for reading and writing input-control values. +* Provide visual feedback using special CSS classes that track the state of the controls. +* Display validation errors to users and enable or disable form controls based on the form status. +* Share information across HTML elements using [template reference variables](guide/template-syntax#template-reference-variables-var). -`app.module.ts` defines the application's root module. In it you identify the external modules you'll use in the application -and declare the components that belong to this module, such as the `HeroFormComponent`. +## Prerequisites -Because template-driven forms are in their own module, you need to add the `FormsModule` to the array of -`imports` for the application module before you can use forms. +Before going further into template-driven forms, you should have a basic understanding of the following. -Update it with the following: +* TypeScript and HTML5 programming. +* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts."). +* The basics of [Angular template syntax](guide/template-syntax "Template syntax guide"). +* The form-design concepts that are presented in [Introduction to Forms](guide/forms-overview "Overview of Angular forms."). - +{@a intro} -
+## Build a template-driven form - There are two changes: +Template-driven forms rely on directives defined in the `FormsModule`. - 1. You import `FormsModule`. +* The `NgModel` directive reconciles value changes in the attached form element with changes in the data model, allowing you to respond to user input with input validation and error handling. - 1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application - access to all of the template-driven forms features, including `ngModel`. +* The `NgForm` directive creates a top-level `FormGroup` instance and binds it to a `
` element to track aggregated form value and validation status. +As soon as you import `FormsModule`, this directive becomes active by default on all `` tags. You don't need to add a special selector. -
+* The `NgModelGroup` directive creates and binds a `FormGroup` instance to a DOM element. -
+### The sample application - If a component, directive, or pipe belongs to a module in the `imports` array, ​_don't_​ re-declare it in the `declarations` array. - If you wrote it and it should belong to this module, ​_do_​ declare it in the `declarations` array. +The sample form in this guide is used by the *Hero Employment Agency* to maintain personal information about heroes. +Every hero needs a job. This form helps the agency match the right hero with the right crisis. + -## Revise *app.component.html* - -`AppComponent` is the application's root component. It will host the new `HeroFormComponent`. - -Replace the contents of its template with the following: +The form highlights some design features that make it easier to use. For instance, the two required fields have a green bar on the left to make them easy to spot. These fields have initial values, so the form is valid and the **Submit** button is enabled. - - -
- - There are only two changes. - The `template` is simply the new element tag identified by the component's `selector` property. - This displays the hero form when the application component is loaded. - Don't forget to remove the `name` field from the class body as well. +As you work with this form, you will learn how to include validation logic, how to customize the presentation with standard CSS, and how to handle error conditions to ensure valid input. +If the user deletes the hero name, for example, the form becomes invalid. The app detects the changed status, and displays a validation error in an attention-grabbing style. +In addition, the **Submit** button is disabled, and the "required" bar to the left of the input control changes from green to red. + -## Create an initial HTML form template +### Step overview -Update the template file with the following contents: +In the course of this tutorial, you bind a sample form to data and handle user input using the following steps. - +1. Build the basic form. + * Define a sample data model. + * Include required infrastructure such as the `FormsModule`. +2. Bind form controls to data properties using the `ngModel` directive and two-way data-binding syntax. + * Examine how `ngModel` reports control states using CSS classes. + * Name controls to make them accessible to `ngModel`. +3. Track input validity and control status using `ngModel`. + * Add custom CSS to provide visual feedback on the status. + * Show and hide validation-error messages. +4. Respond to a native HTML button-click event by adding to the model data. +5. Handle form submission using the [`ngSubmit`(api/forms/NgForm#properties)] output property of the form. + * Disable the **Submit** button until the form is valid. + * After submit, swap out the finished form for different content on the page. -The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and -opening them up for user input in input boxes. +{@a step1} -The *Name* `` control has the HTML5 `required` attribute; -the *Alter Ego* `` control does not because `alterEgo` is optional. +## Build the form -You added a *Submit* button at the bottom with some classes on it for styling. +You can recreate the sample application from the code provided here, or you can examine or download the . -*You're not using Angular yet*. There are no bindings or extra directives, just layout. +1. The provided sample application creates the `Hero` class which defines the data model reflected in the form. -
+ - In template driven forms, if you've imported `FormsModule`, you don't have to do anything - to the `` tag in order to make use of `FormsModule`. Continue on to see how this works. +2. The form layout and details are defined in the `HeroFormComponent` class. -
+ -The `container`, `form-group`, `form-control`, and `btn` classes -come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic. -Bootstrap gives the form a little style. + The component's `selector` value of "app-hero-form" means you can drop this form in a parent +template using the `` tag. -
+3. The following code creates a new hero instance, so that the initial form can show an example hero. -
- Angular forms don't require a style library -
+ - Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or - the styles of any external library. Angular apps can use any CSS library or none at all. + This demo uses dummy data for `model` and `powers`. In a real app, you would inject a data service to get and save real data, or expose these properties as inputs and outputs. -
+4. The application enables the Forms feature and registers the created form component. -To add the stylesheet, open `styles.css` and add the following import line at the top: + - +5. The form is displayed in the application layout defined by the root component's template. -## Add powers with _*ngFor_ + -The hero must choose one superpower from a fixed list of agency-approved powers. -You maintain that list internally (in `HeroFormComponent`). + The initial template defines the layout for a form with two form groups and a submit button. + The form groups correspond to two properties of the Hero data model, name and alterEgo. Each group has a label and a box for user input. -You'll add a `select` to the -form and bind the options to the `powers` list using `ngFor`, -a technique seen previously in the [Displaying Data](guide/displaying-data) page. + * The **Name** `` control element has the HTML5 `required` attribute. + * The **Alter Ego** `` control element does not because `alterEgo` is optional. -Add the following HTML *immediately below* the *Alter Ego* group: + The **Submit** button has some classes on it for styling. + At this point, the form layout is all plain HTML5, with no bindings or directives. - +6. The sample form uses some style classes from [Twitter Bootstrap](http://getbootstrap.com/css/): `container`, `form-group`, `form-control`, and `btn`. + To use these styles, the app's style sheet imports the library. -This code repeats the `
-The diagnostic near the top of the form -confirms that all of your changes are reflected in the model. - -*Delete* the `{{diagnostic}}` binding at the top as it has served its purpose. +The diagnostic near the top of the form confirms that all of your changes are reflected in the model. -## Track control state and validity with _ngModel_ +4. When you have observed the effects, you can delete the `{{diagnostic}}` binding. -Using `ngModel` in a form gives you more than just two-way data binding. It also tells -you if the user touched the control, if the value changed, or if the value became invalid. +## Track control states -The *NgModel* directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state. -You can leverage those class names to change the appearance of the control. +The `NgModel` directive on a control tracks the state of that control. +It tells you if the user touched the control, if the value changed, or if the value became invalid. +Angular sets special CSS classes on the control element to reflect the state, as shown in the following table. @@ -472,38 +314,32 @@ You can leverage those class names to change the appearance of the control.
-Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy` -to the _Name_ `` tag and use it to display the input's CSS classes. +You use these CSS classes to define the styles for your control based on its status. - +### Observe control states -Now run the app and look at the _Name_ input box. -Follow these steps *precisely*: +To see how the classes are added and removed by the framework, open the browser's developer tools and inspect the `` element that represents the hero name. -1. Look but don't touch. -1. Click inside the name box, then click outside it. -1. Add slashes to the end of the name. -1. Erase the name. - -The actions and effects are as follows: - - +1. Using your browser's developer tools, find the `` element that corresponds to the **Name** input box. + You can see that the element has multiple CSS classes in addition to "form-control". -You should see the following transitions and class names: +2. When you first bring it up, the classes indicate that it has a valid value, that the value has not been changed since initialization or reset, and that the control has not been visited since initialization or reset. - + ``` + + ``` -The `ng-valid`/`ng-invalid` pair is the most interesting, because you want to send a -strong visual signal when the values are invalid. You also want to mark required fields. -To create such visual feedback, add definitions for the `ng-*` CSS classes. +3. Take the following actions on the **Name** `` box, and observe which classes appear. + * Look but don't touch. The classes indicate that it is untouched, pristine, and valid. + * Click inside the name box, then click outside it. The control has now been visited, and the element has the `ng-touched` class instead of the `ng-untouched` class. + * Add slashes to the end of the name. It is now touched and dirty. + * Erase the name. This makes the value invalid, so the `ng-invalid` class replaces the `ng-valid` class. -*Delete* the `#spy` template reference variable and the `TODO` as they have served their purpose. +### Create visual feedback for states -## Add custom CSS for visual feedback +The `ng-valid`/`ng-invalid` pair is particularly interesting, because you want to send a +strong visual signal when the values are invalid. +You also want to mark required fields. You can mark required fields and invalid data at the same time with a colored bar on the left of the input box: @@ -512,20 +348,25 @@ on the left of the input box: Invalid Form
-You achieve this effect by adding these class definitions to a new `forms.css` file -that you add to the project as a sibling to `index.html`: +To change the appearance in this way, take the following steps. + +1. Add definitions for the `ng-*` CSS classes. + +2. Add these class definitions to a new `forms.css` file. - +3. Add the new file to the project as a sibling to `index.html`: -Update the `` of `index.html` to include this style sheet: + - +4. In the `index.html` file, update the `` tag to include the new style sheet. -## Show and hide validation error messages + -You can improve the form. The _Name_ input box is required and clearing it turns the bar red. -That says something is wrong but the user doesn't know *what* is wrong or what to do about it. -Leverage the control's state to reveal a helpful message. +### Show and hide validation error messages + +The **Name** input box is required and clearing it turns the bar red. +That indicates that something is wrong, but the user doesn't know what is wrong or what to do about it. +You can provide a helpful message by checking for and responding to the control's state. When the user deletes the name, the form should look like this: @@ -533,166 +374,135 @@ When the user deletes the name, the form should look like this: Name required
-To achieve this effect, extend the `` tag with the following: +The **Hero Power** select box is also required, but it doesn't need this kind of error handling because the selection box already constrains the selection to valid values. -* A [template reference variable](guide/template-syntax#ref-vars). -* The "*is required*" message in a nearby `
`, which you'll display only if the control is invalid. +To define and show an error message when appropriate, take the following steps. -Here's an example of an error message added to the _name_ input box: +1. Extend the `` tag with a template reference variable that you can use to access the input box's Angular control from within the template. In the example, the variable is `#name="ngModel"`. - +
-You need a template reference variable to access the input box's Angular control from within the template. -Here you created a variable called `name` and gave it the value "ngModel". + The template reference variable (`#name`) is set to `"ngModel"` because that is the value of the [`NgModel.exportAs`](api/core/Directive#exportAs) property. This property tells Angular how to link a reference variable to a directive. -
+
- Why "ngModel"? - A directive's [exportAs](api/core/Directive) property - tells Angular how to link the reference variable to the directive. - You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel". +2. Add a `
` that contains a suitable error message. +3. Show or hide the error message by binding properties of the `name` +control to the message `
` element's `hidden` property. -
+ -You control visibility of the name error message by binding properties of the `name` -control to the message `
` element's `hidden` property. +4. Add a conditional error message to the _name_ input box, as in the following example. + + - +
-In this example, you hide the message when the control is valid or pristine; -"pristine" means the user hasn't changed the value since it was displayed in this form. +
Illustrating the "pristine" state
-This user experience is the developer's choice. Some developers want the message to display at all times. +In this example, you hide the message when the control is either valid or *pristine*. +Pristine means the user hasn't changed the value since it was displayed in this form. If you ignore the `pristine` state, you would hide the message only when the value is valid. If you arrive in this component with a new (blank) hero or an invalid hero, you'll see the error message immediately, before you've done anything. -Some developers want the message to display only when the user makes an invalid change. -Hiding the message while the control is "pristine" achieves that goal. -You'll see the significance of this choice when you add a new hero to the form. +You might want the message to display only when the user makes an invalid change. +Hiding the message while the control is in the `pristine` state achieves that goal. +You'll see the significance of this choice when you add a new hero to the form in the next step. -The hero *Alter Ego* is optional so you can leave that be. - -Hero *Power* selection is required. -You can add the same kind of error handling to the `