docs: improve accessibility of reactive-forms example (#41252)
PR Close #41252
|
@ -54,7 +54,7 @@ describe('Reactive forms', () => {
|
|||
describe('Profile Editor', () => {
|
||||
const firstNameInput = getInput('firstName');
|
||||
const streetInput = getInput('street');
|
||||
const addAliasButton = element(by.buttonText('Add Alias'));
|
||||
const addAliasButton = element(by.buttonText('+ Add another alias'));
|
||||
const updateButton = profileEditor.element(by.buttonText('Update Profile'));
|
||||
const profile: Record<string, string | number> = {
|
||||
firstName: 'John',
|
||||
|
@ -121,8 +121,7 @@ describe('Reactive forms', () => {
|
|||
)
|
||||
);
|
||||
|
||||
const aliasInputs = profileEditor.all(by.cssContainingText('label', 'Alias'));
|
||||
const aliasInput = aliasInputs.get(0).element(by.css('input'));
|
||||
const aliasInput = profileEditor.all(by.css('#alias-0'));
|
||||
await aliasInput.sendKeys(aliasText);
|
||||
const formValueElement = profileEditor.all(by.cssContainingText('p', 'Form Value:'));
|
||||
const formValue = await formValueElement.getText();
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
nav a {
|
||||
padding: 1rem;
|
||||
}
|
|
@ -1,19 +1,10 @@
|
|||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
width: 6em;
|
||||
margin: .5em 0;
|
||||
color: #607D8B;
|
||||
font-weight: bold;
|
||||
padding-bottom: .5rem;
|
||||
padding-top: 1rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 2em;
|
||||
font-size: 1em;
|
||||
padding-left: .4em;
|
||||
}
|
||||
button {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
|
|
@ -1,21 +1,12 @@
|
|||
<!-- #docregion control-binding -->
|
||||
<label>
|
||||
Name:
|
||||
<input type="text" [formControl]="name">
|
||||
</label>
|
||||
|
||||
<label for="name">Name: </label>
|
||||
<input id="name" type="text" [formControl]="name">
|
||||
<!-- #enddocregion control-binding -->
|
||||
|
||||
<!-- #docregion display-value -->
|
||||
|
||||
<p>
|
||||
Value: {{ name.value }}
|
||||
</p>
|
||||
<p>Value: {{ name.value }}</p>
|
||||
<!-- #enddocregion display-value -->
|
||||
|
||||
<!-- #docregion update-value -->
|
||||
|
||||
<p>
|
||||
<button (click)="updateName()">Update Name</button>
|
||||
</p>
|
||||
<button (click)="updateName()">Update Name</button>
|
||||
<!-- #enddocregion update-value -->
|
||||
|
|
|
@ -1,65 +1,48 @@
|
|||
<!-- #docplaster -->
|
||||
<!-- #docregion formgroup -->
|
||||
<form [formGroup]="profileForm">
|
||||
|
||||
<label>
|
||||
First Name:
|
||||
<input type="text" formControlName="firstName">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Last Name:
|
||||
<input type="text" formControlName="lastName">
|
||||
</label>
|
||||
<label for="first-name">First Name: </label>
|
||||
<input id="first-name" type="text" formControlName="firstName">
|
||||
|
||||
<label for="last-name">Last Name: </label>
|
||||
<input id="last-name" type="text" formControlName="lastName">
|
||||
|
||||
<!-- #enddocregion formgroup -->
|
||||
<!-- #docregion formgroupname -->
|
||||
<div formGroupName="address">
|
||||
<h3>Address</h3>
|
||||
<h2>Address</h2>
|
||||
|
||||
<label>
|
||||
Street:
|
||||
<input type="text" formControlName="street">
|
||||
</label>
|
||||
<label for="street">Street: </label>
|
||||
<input id="street" type="text" formControlName="street">
|
||||
|
||||
<label>
|
||||
City:
|
||||
<input type="text" formControlName="city">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
State:
|
||||
<input type="text" formControlName="state">
|
||||
</label>
|
||||
<label for="city">City: </label>
|
||||
<input id="city" type="text" formControlName="city">
|
||||
|
||||
<label>
|
||||
Zip Code:
|
||||
<input type="text" formControlName="zip">
|
||||
</label>
|
||||
<label for="state">State: </label>
|
||||
<input id="state" type="text" formControlName="state">
|
||||
|
||||
<label for="zip">Zip Code: </label>
|
||||
<input id="zip" type="text" formControlName="zip">
|
||||
</div>
|
||||
<!-- #enddocregion formgroupname -->
|
||||
|
||||
<div formArrayName="aliases">
|
||||
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
|
||||
<h2>Aliases</h2>
|
||||
<button (click)="addAlias()">+ Add another alias</button>
|
||||
|
||||
<div *ngFor="let address of aliases.controls; let i=index">
|
||||
<!-- The repeated alias template -->
|
||||
<label>
|
||||
Alias:
|
||||
<input type="text" [formControlName]="i">
|
||||
</label>
|
||||
<label for="alias-{{ i }}">Alias: </label>
|
||||
<input id="alias-{{ i }}" type="text" [formControlName]="i">
|
||||
</div>
|
||||
</div>
|
||||
<!-- #docregion formgroup -->
|
||||
<!-- #docregion formgroup -->
|
||||
</form>
|
||||
<!-- #enddocregion formgroup -->
|
||||
|
||||
<p>
|
||||
Form Value: {{ profileForm.value | json }}
|
||||
</p>
|
||||
<p>Form Value: {{ profileForm.value | json }}</p>
|
||||
|
||||
<!-- #docregion patch-value -->
|
||||
<p>
|
||||
<button (click)="updateProfile()">Update Profile</button>
|
||||
</p>
|
||||
<!-- #enddocregion patch-value -->
|
||||
<button (click)="updateProfile()">Update Profile</button>
|
||||
<!-- #enddocregion patch-value -->
|
||||
|
|
|
@ -1,39 +1,11 @@
|
|||
/* ProfileEditorComponent's private CSS styles */
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 24px;
|
||||
|
||||
form {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
width: 6em;
|
||||
margin: .5em 0;
|
||||
color: #607D8B;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input {
|
||||
height: 2em;
|
||||
font-size: 1em;
|
||||
padding-left: .4em;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #eee;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #cfd8dc;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #eee;
|
||||
color: #ccc;
|
||||
cursor: auto;
|
||||
}
|
||||
|
|
|
@ -2,72 +2,54 @@
|
|||
<!-- #docregion ng-submit -->
|
||||
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
|
||||
<!-- #enddocregion ng-submit -->
|
||||
<label>
|
||||
First Name:
|
||||
<input type="text" formControlName="firstName" required>
|
||||
</label>
|
||||
<label for="first-name">First Name: </label>
|
||||
<input id="first-name" type="text" formControlName="firstName" required>
|
||||
|
||||
<label>
|
||||
Last Name:
|
||||
<input type="text" formControlName="lastName">
|
||||
</label>
|
||||
<label for="last-name">Last Name: </label>
|
||||
<input id="last-name" type="text" formControlName="lastName">
|
||||
|
||||
<div formGroupName="address">
|
||||
<h3>Address</h3>
|
||||
<h2>Address</h2>
|
||||
|
||||
<label>
|
||||
Street:
|
||||
<input type="text" formControlName="street">
|
||||
</label>
|
||||
<label for="street">Street: </label>
|
||||
<input id="street" type="text" formControlName="street">
|
||||
|
||||
<label>
|
||||
City:
|
||||
<input type="text" formControlName="city">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
State:
|
||||
<input type="text" formControlName="state">
|
||||
</label>
|
||||
<label for="city">City: </label>
|
||||
<input id="city" type="text" formControlName="city">
|
||||
|
||||
<label>
|
||||
Zip Code:
|
||||
<input type="text" formControlName="zip">
|
||||
</label>
|
||||
<label for="state">State: </label>
|
||||
<input id="state" type="text" formControlName="state">
|
||||
|
||||
<label for="zip">Zip Code: </label>
|
||||
<input id="zip"type="text" formControlName="zip">
|
||||
</div>
|
||||
|
||||
<!-- #docregion formarrayname -->
|
||||
<div formArrayName="aliases">
|
||||
<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>
|
||||
<h2>Aliases</h2>
|
||||
<button (click)="addAlias()">+ Add another alias</button>
|
||||
|
||||
<div *ngFor="let alias of aliases.controls; let i=index">
|
||||
<!-- The repeated alias template -->
|
||||
<label>
|
||||
Alias:
|
||||
<input type="text" [formControlName]="i">
|
||||
</label>
|
||||
<label for="alias-{{ i }}">Alias:</label>
|
||||
<input id="alias-{{ i }}" type="text" [formControlName]="i">
|
||||
</div>
|
||||
</div>
|
||||
<!-- #enddocregion formarrayname -->
|
||||
|
||||
|
||||
<!-- #docregion submit-button -->
|
||||
<p>Complete the form to enable button.</p>
|
||||
<button type="submit" [disabled]="!profileForm.valid">Submit</button>
|
||||
<!-- #enddocregion submit-button -->
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
Form Value: {{ profileForm.value | json }}
|
||||
</p>
|
||||
<p>Form Value: {{ profileForm.value | json }}</p>
|
||||
|
||||
<!-- #docregion display-status -->
|
||||
|
||||
<p>
|
||||
Form Status: {{ profileForm.status }}
|
||||
</p>
|
||||
<p>Form Status: {{ profileForm.status }}</p>
|
||||
<!-- #enddocregion display-status -->
|
||||
|
||||
<p>
|
||||
<button (click)="updateProfile()">Update Profile</button>
|
||||
</p>
|
||||
<button (click)="updateProfile()">Update Profile</button>
|
||||
|
|
|
@ -13,7 +13,7 @@ Try this <live-example title="Reactive Forms in Stackblitz">Reactive Forms live-
|
|||
Before going further into reactive forms, you should have a basic understanding of the following:
|
||||
|
||||
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") programming.
|
||||
* Angular app-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
* Angular application-design fundamentals, as described in [Angular Concepts](guide/architecture "Introduction to Angular concepts.").
|
||||
* The form-design concepts that are presented in [Introduction to Forms](guide/forms-overview "Overview of Angular forms.").
|
||||
|
||||
{@a intro}
|
||||
|
@ -85,7 +85,7 @@ The form control assigned to `name` is displayed when the component is added to
|
|||
<code-example path="reactive-forms/src/app/app.component.1.html" region="app-name-editor" header="src/app/app.component.html (name editor)"></code-example>
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/reactive-forms/name-editor-1.png" alt="Name Editor">
|
||||
<img src="generated/images/guide/reactive-forms/name-editor-1.png" alt="Name Editor, which has a name label and an input so the user can enter a name">
|
||||
</div>
|
||||
|
||||
{@a display-value}
|
||||
|
@ -126,7 +126,7 @@ Update the template with a button to simulate a name update. When you click the
|
|||
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.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/reactive-forms/name-editor-2.png" alt="Name Editor Update">
|
||||
<img src="generated/images/guide/reactive-forms/name-editor-2.gif" alt="Name Editor Update with a name label, the name Nancy in the input, text specifying that the value of the input is Nancy and an Update Name button">
|
||||
</div>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
@ -217,7 +217,7 @@ To display the `ProfileEditor` component that contains the form, add it to a com
|
|||
`ProfileEditor` allows you to manage the form control instances for the `firstName` and `lastName` controls within the form group instance.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-1.png" alt="Profile Editor">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-1.gif" alt="Profile Editor with labels and inputs for first and last name as well as a submit button">
|
||||
</div>
|
||||
|
||||
{@a nested-groups}
|
||||
|
@ -254,7 +254,7 @@ Add the `address` form group containing the `street`, `city`, `state`, and `zip`
|
|||
The `ProfileEditor` form is displayed as one group, but the model is broken down further to represent the logical grouping areas.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-2.png" alt="Profile Editor Update">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-2.png" alt="Profile editor update adding address inputs, instructive text for filling out the form to enable the submit button, and a disabled submit button">
|
||||
</div>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
@ -388,7 +388,7 @@ Display the current status of `profileForm` using interpolation.
|
|||
<code-example path="reactive-forms/src/app/profile-editor/profile-editor.component.html" region="display-status" header="src/app/profile-editor/profile-editor.component.html (display status)"></code-example>
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-3.png" alt="Profile Editor Validation">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-3.png" alt="Profile Editor with validation status of invalid">
|
||||
</div>
|
||||
|
||||
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.
|
||||
|
@ -466,7 +466,7 @@ Add the template HTML below after the `<div>` closing the `formGroupName` elemen
|
|||
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.
|
||||
|
||||
<div class="lightbox">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-4.png" alt="Profile Editor Aliases">
|
||||
<img src="generated/images/guide/reactive-forms/profile-editor-4.png" alt="Profile Editor with aliases section, which includes an alias label, input, and button for adding another alias text input">
|
||||
</div>
|
||||
|
||||
Each time a new alias instance is added, the new form array instance is provided its control based on the index. This allows you to track each individual control when calculating the status and value of the root control.
|
||||
|
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 7.5 KiB |
After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 18 KiB |