diff --git a/adev/src/content/guide/forms/BUILD.bazel b/adev/src/content/guide/forms/BUILD.bazel
new file mode 100644
index 00000000000..7012bb99966
--- /dev/null
+++ b/adev/src/content/guide/forms/BUILD.bazel
@@ -0,0 +1,52 @@
+load("@npm//@angular/build-tooling/bazel/markdown_to_html:markdown_to_html.bzl", "markdown_to_html")
+
+markdown_to_html(
+ name = "forms",
+ srcs = glob([
+ "*.md",
+ ]),
+ data = [
+ "//adev/src/assets/images:overview.svg",
+ "//adev/src/content/examples/dynamic-form:src/app/app.component.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/dynamic-form.component.html",
+ "//adev/src/content/examples/dynamic-form:src/app/dynamic-form.component.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/dynamic-form-question.component.html",
+ "//adev/src/content/examples/dynamic-form:src/app/dynamic-form-question.component.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/question.service.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/question-base.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/question-control.service.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/question-dropdown.ts",
+ "//adev/src/content/examples/dynamic-form:src/app/question-textbox.ts",
+ "//adev/src/content/examples/form-validation:src/app/reactive/actor-form-reactive.component.1.ts",
+ "//adev/src/content/examples/form-validation:src/app/reactive/actor-form-reactive.component.2.ts",
+ "//adev/src/content/examples/form-validation:src/app/reactive/actor-form-reactive.component.html",
+ "//adev/src/content/examples/form-validation:src/app/shared/forbidden-name.directive.ts",
+ "//adev/src/content/examples/form-validation:src/app/shared/role.directive.ts",
+ "//adev/src/content/examples/form-validation:src/app/shared/unambiguous-role.directive.ts",
+ "//adev/src/content/examples/form-validation:src/app/template/actor-form-template.component.html",
+ "//adev/src/content/examples/form-validation:src/assets/forms.css",
+ "//adev/src/content/examples/forms:src/app/actor.ts",
+ "//adev/src/content/examples/forms:src/app/actor-form/actor-form.component.html",
+ "//adev/src/content/examples/forms:src/app/actor-form/actor-form.component.ts",
+ "//adev/src/content/examples/forms:src/app/app.component.html",
+ "//adev/src/content/examples/forms:src/app/app.component.ts",
+ "//adev/src/content/examples/forms:src/app/app.module.ts",
+ "//adev/src/content/examples/forms:src/assets/forms.css",
+ "//adev/src/content/examples/forms:src/index.html",
+ "//adev/src/content/examples/forms:src/main.ts",
+ "//adev/src/content/examples/forms:src/styles.1.css",
+ "//adev/src/content/examples/forms-overview:src/app/reactive/favorite-color/favorite-color.component.spec.ts",
+ "//adev/src/content/examples/forms-overview:src/app/reactive/favorite-color/favorite-color.component.ts",
+ "//adev/src/content/examples/forms-overview:src/app/template/favorite-color/favorite-color.component.spec.ts",
+ "//adev/src/content/examples/forms-overview:src/app/template/favorite-color/favorite-color.component.ts",
+ "//adev/src/content/examples/reactive-forms:src/app/app.component.1.html",
+ "//adev/src/content/examples/reactive-forms:src/app/app.module.ts",
+ "//adev/src/content/examples/reactive-forms:src/app/name-editor/name-editor.component.html",
+ "//adev/src/content/examples/reactive-forms:src/app/name-editor/name-editor.component.ts",
+ "//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.1.html",
+ "//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.1.ts",
+ "//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.2.ts",
+ "//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.html",
+ "//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.ts",
+ ],
+)
diff --git a/adev/src/content/guide/forms/dynamic-forms.md b/adev/src/content/guide/forms/dynamic-forms.md
index eb4f319aa19..f8e982cefe2 100644
--- a/adev/src/content/guide/forms/dynamic-forms.md
+++ b/adev/src/content/guide/forms/dynamic-forms.md
@@ -35,8 +35,8 @@ To give the application access reactive forms directives, import `ReactiveFormsM
The following code from the example shows the setup in the root module.
-
-
+
+
## Create a form object model
@@ -49,7 +49,7 @@ The example includes the `DynamicFormQuestionComponent`, which defines a questio
The following `QuestionBase` is a base class for a set of controls that can represent the question and its answer in the form.
-
+
### Define control classes
@@ -58,11 +58,11 @@ When you create the form template in the next step, you instantiate these specif
The `TextboxQuestion` control type is represented in a form template using an `` element. It presents a question and lets users enter input. The `type` attribute of the element is defined based on the `type` field specified in the `options` argument (for example `text`, `email`, `url`).
-
+
The `DropdownQuestion` control type presents a list of choices in a select box.
-
+
### Compose form groups
@@ -70,7 +70,7 @@ A dynamic form uses a service to create grouped sets of input controls, based on
The following `QuestionControlService` collects a set of `FormGroup` instances that consume the metadata from the question model.
You can specify default values and validation rules.
-
+
## Compose dynamic form contents
@@ -82,8 +82,8 @@ The form relies on a [`[formGroup]` directive](api/forms/FormGroupDirective "API
The `DynamicFormQuestionComponent` creates form groups and populates them with controls defined in the question model, specifying display and validation rules.
-
-
+
+
The goal of the `DynamicFormQuestionComponent` is to present question types defined in your model.
@@ -102,7 +102,7 @@ To maintain the questionnaire as requirements change, you only need to add, upda
The `QuestionService` supplies a set of questions in the form of an array bound to `@Input()` questions.
-
+
## Create a dynamic form template
@@ -111,15 +111,15 @@ The `DynamicFormComponent` component is the entry point and the main container f
The `DynamicFormComponent` component presents a list of questions by binding each one to an `` element that matches the `DynamicFormQuestionComponent`.
-
-
+
+
### Display the form
To display an instance of the dynamic form, the `AppComponent` shell template passes the `questions` array returned by the `QuestionService` to the form container component, ``.
-
+
This separation of model and data lets you repurpose the components for any type of survey, as long as it's compatible with the _question_ object model.
diff --git a/adev/src/content/guide/forms/form-validation.md b/adev/src/content/guide/forms/form-validation.md
index 5b94249e2a3..1020dc993be 100644
--- a/adev/src/content/guide/forms/form-validation.md
+++ b/adev/src/content/guide/forms/form-validation.md
@@ -13,7 +13,7 @@ Every time the value of a form control changes, Angular runs validation and gene
You can then inspect the control's state by exporting `ngModel` to a local template variable.
The following example exports `NgModel` into a variable called `name`:
-
+
Notice the following features illustrated by the example.
@@ -63,7 +63,7 @@ For a full list of built-in validators, see the [Validators](api/forms/Validator
To update the actor form to be a reactive form, use some of the same
built-in validators —this time, in function form, as in the following example.
-
+
In this example, the `name` control sets up two built-in validators —`Validators.required` and `Validators.minLength(4)`— and one custom validator, `forbiddenNameValidator`.
@@ -75,7 +75,7 @@ In a reactive form, you can always access any form control through the `get` met
If you look at the template for the `name` input again, it is fairly similar to the template-driven example.
-
+
This form differs from the template-driven version in that it no longer exports any directives. Instead, it uses the `name` getter defined in the component class.
@@ -88,7 +88,7 @@ The built-in validators don't always match the exact use case of your applicatio
Consider the `forbiddenNameValidator` function from the previous example.
Here's what the definition of that function looks like.
-
+
The function is a factory that takes a regular expression to detect a *specific* forbidden name and returns a validator function.
@@ -106,7 +106,7 @@ In the case of an observable, the observable must complete, at which point the f
In reactive forms, add a custom validator by passing the function directly to the `FormControl`.
-
+
### Adding custom validators to template-driven forms
@@ -116,17 +116,17 @@ For example, the corresponding `ForbiddenValidatorDirective` serves as a wrapper
Angular recognizes the directive's role in the validation process because the directive registers itself with the `NG_VALIDATORS` provider, as shown in the following example.
`NG_VALIDATORS` is a predefined provider with an extensible collection of validators.
-
+
The directive class then implements the `Validator` interface, so that it can easily integrate with Angular forms.
Here is the rest of the directive to help you get an idea of how it all comes together.
-
+
Once the `ForbiddenValidatorDirective` is ready, you can add its selector, `appForbiddenName`, to any input element to activate it.
For example:
-
+
HELPFUL: Notice that the custom validation directive is instantiated with `useExisting` rather than `useClass`.
The registered validator must be *this instance* of the `ForbiddenValidatorDirective` —the instance in the form with its `forbiddenName` property bound to "bob".
@@ -151,7 +151,7 @@ The following classes are currently supported.
In the following example, the actor form uses the `.ng-valid` and `.ng-invalid` classes to
set the color of each form control's border.
-
+
## Cross-field validation
@@ -199,7 +199,7 @@ const actorForm = new FormGroup({
The validator code is as follows.
-
+
The `unambiguousRoleValidator` validator implements the `ValidatorFn` interface.
It takes an Angular control object as an argument and returns either null if the form is valid, or `ValidationErrors` otherwise.
@@ -211,7 +211,7 @@ If they do match, the actor's role is ambiguous and the validator must mark the
To provide better user experience, the template shows an appropriate error message when the form is invalid.
-
+
This `*ngIf` displays the error if the `FormGroup` has the cross validation error returned by the `unambiguousRoleValidator` validator, but only if the user finished [interacting with the form](#control-status-css-classes).
@@ -220,16 +220,16 @@ This `*ngIf` displays the error if the `FormGroup` has the cross validation erro
For a template-driven form, you must create a directive to wrap the validator function.
You provide that directive as the validator using the [`NG_VALIDATORS` token](/api/forms/NG_VALIDATORS), as shown in the following example.
-
+
You must add the new directive to the HTML template.
Because the validator must be registered at the highest level in the form, the following template puts the directive on the `form` tag.
-
+
To provide better user experience, an appropriate error message appears when the form is invalid.
-
+
This is the same in both template-driven and reactive forms.
@@ -266,7 +266,7 @@ To validate the potential role entry, the validator must initiate an asynchronou
The following code creates the validator class, `UniqueRoleValidator`, which implements the `AsyncValidator` interface.
-
+
The constructor injects the `ActorsService`, which defines the following interface.
@@ -297,7 +297,7 @@ The `pending` flag is set to `false`, and the form validity is updated.
To use an async validator in reactive forms, begin by injecting the validator into the constructor of the component class.
-
+
Then, pass the validator function directly to the `FormControl` to apply it.
@@ -305,7 +305,7 @@ In the following example, the `validate` function of `UnambiguousRoleValidator`
The value of `asyncValidators` can be either a single async validator function, or an array of functions.
To learn more about `FormControl` options, see the [AbstractControlOptions](api/forms/AbstractControlOptions) API reference.
-
+
### Adding async validators to template-driven forms
@@ -313,11 +313,11 @@ To use an async validator in template-driven forms, create a new directive and r
In the example below, the directive injects the `UniqueRoleValidator` class that contains the actual validation logic and invokes it in the `validate` function, triggered by Angular when validation should happen.
-
+
Then, as with synchronous validators, add the directive's selector to an input to activate it.
-
+
### Optimizing performance of async validators
diff --git a/adev/src/content/guide/forms/overview.md b/adev/src/content/guide/forms/overview.md
index 9157c616879..a82859e5c9a 100644
--- a/adev/src/content/guide/forms/overview.md
+++ b/adev/src/content/guide/forms/overview.md
@@ -1,4 +1,4 @@
-
+
Handling user input with forms is the cornerstone of many common applications.
@@ -70,7 +70,7 @@ The `[formControl]` directive links the explicitly created `FormControl` instanc
The following component implements an input field for a single control, using reactive forms.
In this example, the form model is the `FormControl` instance.
-
+
IMPORTANT: In reactive forms, the form model is the source of truth; it provides the value and status of the form element at any given point in time, through the `[formControl]` directive on the `` element.
@@ -81,7 +81,7 @@ The directive `NgModel` creates and manages a `FormControl` instance for a given
The following component implements the same input field for a single control, using template-driven forms.
-
+
IMPORTANT: In a template-driven form the source of truth is the template. The `NgModel` directive automatically manages the `FormControl` instance for you.
@@ -107,7 +107,8 @@ The view-to-model diagram shows how data flows when an input field's value is ch
1. The `FormControl` instance emits the new value through the `valueChanges` observable.
1. Any subscribers to the `valueChanges` observable receive the new value.
-```mermaid
+
+```
flowchart TB
U{User}
I("<input>")
@@ -127,7 +128,8 @@ The model-to-view diagram shows how a programmatic change to the model is propag
1. Any subscribers to the `valueChanges` observable receive the new value.
1. The control value accessor on the form input element updates the element with the new value.
-```mermaid
+
+```
flowchart TB
U{User}
I(<input>)
@@ -154,7 +156,8 @@ The view-to-model diagram shows how data flows when an input field's value is ch
1. The control value accessor also calls the `NgModel.viewToModelUpdate()` method which emits an `ngModelChange` event.
1. Because the component template uses two-way data binding for the `favoriteColor` property, the `favoriteColor` property in the component is updated to the value emitted by the `ngModelChange` event \(*Blue*\).
-```mermaid
+
+```
flowchart TB
U{User}
I(<input>)
@@ -185,7 +188,8 @@ The model-to-view diagram shows how data flows from model to view when the `favo
1. Any subscribers to the `valueChanges` observable receive the new value.
1. The control value accessor updates the form input element in the view with the latest `favoriteColor` value.
-```mermaid
+
+```
flowchart TB
C(Component)
P(Property bound to NgModel)
@@ -266,7 +270,7 @@ The first example performs the following steps to verify the view-to-model data
1. Set the new value for the input to *Red*, and dispatch the "input" event on the form input element.
1. Assert that the component's `favoriteColorControl` value matches the value from the input.
-
+
The next example performs the following steps to verify the model-to-view data flow.
@@ -274,7 +278,7 @@ The next example performs the following steps to verify the model-to-view data f
1. Query the view for the form input element.
1. Assert that the new value set on the control matches the value in the input.
-
+
### Testing template-driven forms
@@ -284,7 +288,7 @@ The following tests use the favorite color components mentioned earlier to verif
The following test verifies the data flow from view to model.
-
+
Here are the steps performed in the view to model test.
@@ -295,7 +299,7 @@ Here are the steps performed in the view to model test.
The following test verifies the data flow from model to view.
-
+
Here are the steps performed in the model to view test.
diff --git a/adev/src/content/guide/forms/reactive-forms.md b/adev/src/content/guide/forms/reactive-forms.md
index 6c1a395f3d6..8c0060dfe62 100644
--- a/adev/src/content/guide/forms/reactive-forms.md
+++ b/adev/src/content/guide/forms/reactive-forms.md
@@ -38,13 +38,13 @@ In the example, the user enters their name into an input field, captures that in
To use reactive form controls, import `ReactiveFormsModule` from the `@angular/forms` package and add it to your NgModule's `imports` array.
-
+
Use the CLI command `ng generate component` to generate a component in your project to host the control.
-
+
Use the constructor of `FormControl` to set its initial value, which in this case is an empty string. By creating these controls in your component class, you get immediate access to listen for, update, and validate the state of the form input.
@@ -52,7 +52,7 @@ Use the constructor of `FormControl` to set its initial value, which in this cas
After you create the control in the component class, you must associate it with a form control element in the template. Update the template with the form control using the `formControl` binding provided by `FormControlDirective`, which is also included in the `ReactiveFormsModule`.
-
+
Using the template binding syntax, the form control is now registered to the `name` input element in the template. The form control and DOM element communicate with each other: the view reflects changes in the model, and the model reflects changes in the view.
@@ -60,7 +60,7 @@ Using the template binding syntax, the form control is now registered to the `na
The `FormControl` assigned to the `name` property is displayed when the `` component is added to a template.
-
+
@@ -73,7 +73,7 @@ You can display the value in the following ways.
The following example shows you how to display the current value using interpolation in the template.
-
+
The displayed value changes as you update the form control element.
@@ -90,12 +90,12 @@ For example, when retrieving form data from a backend API or service, use the `s
The following example adds a method to the component class to update the value of the control to *Nancy* using the `setValue()` method.
-
+
Update the template with a button to simulate a name update.
When you click the **Update Name** button, the value entered in the form control element is reflected as its current value.
-
+
The form model is the source of truth for the control, so when you click the button, the value of the input is changed within the component class, overriding its current value.
@@ -122,7 +122,7 @@ Generate a `ProfileEditor` component and import the `FormGroup` and `FormControl
ng generate component ProfileEditor
-
+
To add a form group to this component, take the following steps.
@@ -137,7 +137,7 @@ Create a property in the component class named `profileForm` and set the propert
For the profile form, add two form control instances with the names `firstName` and `lastName`
-
+
The individual form controls are now collected within a group. A `FormGroup` instance provides its model value as an object reduced from the values of each control in the group. A form group instance has the same properties (such as `value` and `untouched`) and methods (such as `setValue()`) as a form control instance.
@@ -145,7 +145,7 @@ The individual form controls are now collected within a group. A `FormGroup` ins
A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view.
-
+
Just as a form group contains a group of controls, the *profileForm* `FormGroup` is bound to the `form` element with the `FormGroup` directive, creating a communication layer between the model and the form containing the inputs. The `formControlName` input provided by the `FormControlName` directive binds each individual input to the form control defined in `FormGroup`. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value.
@@ -153,17 +153,17 @@ Just as a form group contains a group of controls, the *profileForm* `FormGroup`
The `ProfileEditor` component accepts input from the user, but in a real scenario you want to capture the form value and make available for further processing outside the component. The `FormGroup` directive listens for the `submit` event emitted by the `form` element and emits an `ngSubmit` event that you can bind to a callback function. Add an `ngSubmit` event listener to the `form` tag with the `onSubmit()` callback method.
-
+
The `onSubmit()` method in the `ProfileEditor` component captures the current value of `profileForm`. Use `EventEmitter` to keep the form encapsulated and to provide the form value outside the component. The following example uses `console.warn` to log a message to the browser console.
-
+
The `submit` event is emitted by the `form` tag using the built-in DOM event. You trigger the event by clicking a button with `submit` type. This lets the user press the **Enter** key to submit the completed form.
Use a `button` element to add a button to the bottom of the form to trigger the form submission.
-
+
The button in the preceding snippet also has a `disabled` binding attached to it to disable the button when `profileForm` is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the [Validating form input](#validating-form-input) section.
@@ -171,7 +171,7 @@ The button in the preceding snippet also has a `disabled` binding attached to it
To display the `ProfileEditor` component that contains the form, add it to a component template.
-
+
`ProfileEditor` lets you manage the form control instances for the `firstName` and `lastName` controls within the form group instance.
@@ -195,7 +195,7 @@ A name and address are typical examples of such nested groups, and are used in t
To create a nested group in `profileForm`, add a nested `address` element to the form group instance.
-
+
In this example, `address group` combines the current `firstName` and `lastName` controls with the new `street`, `city`, `state`, and `zip` controls. Even though the `address` element in the form group is a child of the overall `profileForm` element in the form group, the same rules apply with value and status changes. Changes in status and value from the nested form group propagate to the parent form group, maintaining consistency with the overall model.
@@ -203,7 +203,7 @@ In this example, `address group` combines the current `firstName` and `lastName`
After you update the model in the component class, update the template to connect the form group instance and its input elements. Add the `address` form group containing the `street`, `city`, `state`, and `zip` fields to the `ProfileEditor` template.
-
+
The `ProfileEditor` form is displayed as one group, but the model is broken down further to represent the logical grouping areas.
@@ -227,11 +227,11 @@ The strict checks of the `setValue()` method help catch nesting errors in comple
In `ProfileEditorComponent`, use the `updateProfile` method with the following example to update the first name and street address for the user.
-
+
Simulate an update by adding a button to the template to update the user profile on demand.
-
+
When a user clicks the button, the `profileForm` model is updated with new values for `firstName` and `street`. Notice that `street` is provided in an object inside the `address` property.
This is necessary because the `patchValue()` method applies the update against the model structure.
@@ -254,28 +254,28 @@ The following examples show how to refactor the `ProfileEditor` component to use
Import the `FormBuilder` class from the `@angular/forms` package.
-
+
The `FormBuilder` service is an injectable provider that is provided with the reactive forms module. Inject this dependency by adding it to the component constructor.
-
+
The `FormBuilder` service has three methods: `control()`, `group()`, and `array()`. These are factory methods for generating instances in your component classes including form controls, form groups, and form arrays. Use the `group` method to create the `profileForm` controls.
-
+
In the preceding example, you use the `group()` method with the same object to define the properties in the model. The value for each control name is an array containing the initial value as the first item in the array.
Tip: You can define the control with just the initial value, but if your controls need sync or async validation, add sync and async validators as the second and third items in the array. Compare using the form builder to creating the instances manually.
-
-
+
+
@@ -302,13 +302,13 @@ Reactive forms include a set of validator functions for common use cases. These
Import the `Validators` class from the `@angular/forms` package.
-
+
In the `ProfileEditor` component, add the `Validators.required` static method as the second item in the array for the `firstName` control.
-
+
@@ -316,7 +316,7 @@ When you add a required field to the form control, its initial status is invalid
Display the current status of `profileForm` using interpolation.
-
+
The **Submit** button is disabled because `profileForm` is invalid due to the required `firstName` form control. After you fill out the `firstName` input, the form becomes valid and the **Submit** button is enabled.
@@ -343,7 +343,7 @@ The following example shows you how to manage an array of *aliases* in `ProfileE
Import the `FormArray` class from `@angular/forms` to use for type information. The `FormBuilder` service is ready to create a `FormArray` instance.
-
+
@@ -351,7 +351,7 @@ You can initialize a form array with any number of controls, from zero to many,
Use the `FormBuilder.array()` method to define the array, and the `FormBuilder.control()` method to populate the array with an initial control.
-
+
The aliases control in the form group instance is now populated with a single control until more controls are added dynamically.
@@ -361,11 +361,11 @@ A getter provides access to the aliases in the form array instance compared to r
Use the getter syntax to create an `aliases` class property to retrieve the alias's form array control from the parent form group.
-
+
Because the returned control is of the type `AbstractControl`, you need to provide an explicit type to access the method syntax for the form array instance. Define a method to dynamically insert an alias control into the alias's form array. The `FormArray.push()` method inserts the control as a new item in the array.
-
+
In the template, each control is displayed as a separate input field.
@@ -377,7 +377,7 @@ To attach the aliases from your form model, you must add it to the template. Sim
Add the following template HTML after the `
` closing the `formGroupName` element.
-
+
The `*ngFor` directive iterates over each form control instance provided by the aliases form array instance. Because form array elements are unnamed, you assign the index to the `i` variable and pass it to each control to bind it to the `formControlName` input.
diff --git a/adev/src/content/guide/forms/template-driven-forms.md b/adev/src/content/guide/forms/template-driven-forms.md
index 1e96a63e66c..f01de18cc20 100644
--- a/adev/src/content/guide/forms/template-driven-forms.md
+++ b/adev/src/content/guide/forms/template-driven-forms.md
@@ -59,28 +59,28 @@ In the course of this tutorial, you bind a sample form to data and handle user i
1. The provided sample application creates the `Actor` class which defines the data model reflected in the form.
-
+
1. The form layout and details are defined in the `ActorFormComponent` class.
-
+
The component's `selector` value of "app-actor-form" means you can drop this form in a parent template using the `` tag.
1. The following code creates a new actor instance, so that the initial form can show an example actor.
-
+
This demo uses dummy data for `model` and `skills`.
In a real app, you would inject a data service to get and save real data, or expose these properties as inputs and outputs.
1. The application enables the Forms feature and registers the created form component.
-
+
1. The form is displayed in the application layout defined by the root component's template.
-
+
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 Actor data model, name and studio.
@@ -95,12 +95,12 @@ In the course of this tutorial, you bind a sample form to data and handle user i
1. The sample form uses some style classes from [Twitter Bootstrap](https://getbootstrap.com/css): `container`, `form-group`, `form-control`, and `btn`.
To use these styles, the application's style sheet imports the library.
-
+
1. The form requires that an actor's skill is chosen from a predefined list of `skills` maintained internally in `ActorFormComponent`.
The Angular [NgForOf directive](api/common/NgForOf "API reference") iterates over the data values to populate the `