diff --git a/modules/@angular/examples/forms/ts/nestedFormGroup/e2e_test/nested_form_group_spec.ts b/modules/@angular/examples/forms/ts/nestedFormGroup/e2e_test/nested_form_group_spec.ts new file mode 100644 index 0000000000..a879c1a926 --- /dev/null +++ b/modules/@angular/examples/forms/ts/nestedFormGroup/e2e_test/nested_form_group_spec.ts @@ -0,0 +1,43 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {verifyNoBrowserErrors} from '../../../../_common/e2e_util'; + +describe('nestedFormGroup example', () => { + afterEach(verifyNoBrowserErrors); + let firstInput: ElementFinder; + let lastInput: ElementFinder; + let button: ElementFinder; + + beforeEach(() => { + browser.get('/forms/ts/nestedFormGroup/index.html'); + firstInput = element(by.css('[formControlName="first"]')); + lastInput = element(by.css('[formControlName="last"]')); + button = element(by.css('button:not([type="submit"])')); + }); + + it('should populate the UI with initial values', () => { + expect(firstInput.getAttribute('value')).toEqual('Nancy'); + expect(lastInput.getAttribute('value')).toEqual('Drew'); + }); + + it('should show the error when name is invalid', () => { + firstInput.click(); + firstInput.clear(); + firstInput.sendKeys('a'); + + expect(element(by.css('p')).getText()).toEqual('Name is invalid.'); + }); + + it('should set the value programmatically', () => { + button.click(); + expect(firstInput.getAttribute('value')).toEqual('Bess'); + expect(lastInput.getAttribute('value')).toEqual('Marvin'); + }); + +}); diff --git a/modules/@angular/examples/forms/ts/nestedFormGroup/module.ts b/modules/@angular/examples/forms/ts/nestedFormGroup/module.ts new file mode 100644 index 0000000000..5773d5281f --- /dev/null +++ b/modules/@angular/examples/forms/ts/nestedFormGroup/module.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {ReactiveFormsModule} from '@angular/forms'; +import {BrowserModule} from '@angular/platform-browser'; +import {NestedFormGroupComp} from './nested_form_group_example'; + +@NgModule({ + imports: [BrowserModule, ReactiveFormsModule], + declarations: [NestedFormGroupComp], + bootstrap: [NestedFormGroupComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/nestedFormGroup/nested_form_group_example.ts b/modules/@angular/examples/forms/ts/nestedFormGroup/nested_form_group_example.ts new file mode 100644 index 0000000000..6b7094045d --- /dev/null +++ b/modules/@angular/examples/forms/ts/nestedFormGroup/nested_form_group_example.ts @@ -0,0 +1,52 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +// #docregion Component +import {Component} from '@angular/core'; +import {FormControl, FormGroup, Validators} from '@angular/forms'; + +@Component({ + selector: 'example-app', + template: ` +
+

Name is invalid.

+ +
+ + +
+ + +
+ + +`, +}) +export class NestedFormGroupComp { + form = new FormGroup({ + name: new FormGroup({ + first: new FormControl('Nancy', Validators.minLength(2)), + last: new FormControl('Drew', Validators.required) + }), + email: new FormControl() + }); + + get first() { return this.form.get('name.first'); } + + get name() { return this.form.get('name'); } + + onSubmit() { + console.log(this.first.value); // 'Nancy' + console.log(this.name.value); // {first: 'Nancy', last: 'Drew'} + console.log(this.form.value); // {name: {first: 'Nancy', last: 'Drew'}, email: ''} + console.log(this.form.status); // VALID + } + + setPreset() { this.name.setValue({first: 'Bess', last: 'Marvin'}); } +} +// #enddocregion diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts b/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts index 071d108979..4107cb3206 100644 --- a/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts +++ b/modules/@angular/forms/src/directives/reactive_directives/form_group_name.ts @@ -24,49 +24,46 @@ export const formGroupNameProvider: any = { }; /** - * Syncs an existing form group to a DOM element. + * @whatItDoes Syncs a nested {@link FormGroup} to a DOM element. * - * This directive can only be used as a child of {@link FormGroupDirective}. It also requires - * importing the {@link ReactiveFormsModule}. + * @howToUse * - * ```typescript - * @Component({ - * selector: 'my-app', - * template: ` - *
- *

Angular FormGroup Example

- *
- *
- *

Enter your name:

- *

First:

- *

Middle:

- *

Last:

- *
- *

Name value:

- *
{{ myForm.get('name') | json }}
- *

Name is {{myForm.get('name')?.valid ? "valid" : "invalid"}}

- *

What's your favorite food?

- *

- *

Form value

- *
 {{ myForm | json }} 
- *
- *
- * ` - * }) - * export class App { - * myForm = new FormGroup({ - * name: new FormGroup({ - * first: new FormControl('', Validators.required), - * middle: new FormControl(''), - * last: new FormControl('', Validators.required) - * }), - * food: new FormControl() - * }); - * } - * ``` + * This directive can only be used with a parent {@link FormGroupDirective} (selector: + * `[formGroup]`). * - * This example syncs the form group for the user's name. The value and validation state of - * this group can be accessed separately from the overall form. + * It accepts the string name of the nested {@link FormGroup} you want to link, and + * will look for a {@link FormGroup} registered with that name in the parent + * {@link FormGroup} instance you passed into {@link FormGroupDirective}. + * + * Nested form groups can come in handy when you want to validate a sub-group of a + * form separately from the rest or when you'd like to group the values of certain + * controls into their own nested object. + * + * **Access the group**: You can access the associated {@link FormGroup} using the + * {@link AbstractControl.get} method. Ex: `this.form.get('name')`. + * + * You can also access individual controls within the group using dot syntax. + * Ex: `this.form.get('name.first')` + * + * **Get the value**: the `value` property is always synced and available on the + * {@link FormGroup}. See a full list of available properties in {@link AbstractControl}. + * + * **Set the value**: You can set an initial value for each child control when instantiating + * the {@link FormGroup}, or you can set it programmatically later using + * {@link AbstractControl.setValue} or {@link AbstractControl.patchValue}. + * + * **Listen to value**: If you want to listen to changes in the value of the group, you can + * subscribe to the {@link AbstractControl.valueChanges} event. You can also listen to + * {@link AbstractControl.statusChanges} to be notified when the validation status is + * re-calculated. + * + * ### Example + * + * {@example forms/ts/nestedFormGroup/nested_form_group_example.ts region='Component'} + * + * * **npm package**: `@angular/forms` + * + * * **NgModule**: `ReactiveFormsModule` * * @stable */