diff --git a/modules/@angular/examples/forms/ts/reactiveSelectControl/e2e_test/reactive_select_control_spec.ts b/modules/@angular/examples/forms/ts/reactiveSelectControl/e2e_test/reactive_select_control_spec.ts new file mode 100644 index 0000000000..a1a4738234 --- /dev/null +++ b/modules/@angular/examples/forms/ts/reactiveSelectControl/e2e_test/reactive_select_control_spec.ts @@ -0,0 +1,36 @@ +/** + * @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('reactiveSelectControl example', () => { + afterEach(verifyNoBrowserErrors); + let select: protractor.ElementFinder; + let options: protractor.ElementArrayFinder; + let p: protractor.ElementFinder; + + beforeEach(() => { + browser.get('/forms/ts/reactiveSelectControl/index.html'); + select = element(by.css('select')); + options = element.all(by.css('option')); + p = element(by.css('p')); + }); + + it('should populate the initial selection', () => { + expect(select.getAttribute('value')).toEqual('3: Object'); + expect(options.get(3).getAttribute('selected')).toBe('true'); + }); + + it('should update the model when the value changes in the UI', () => { + select.click(); + options.get(0).click(); + + expect(p.getText()).toEqual('Form value: { "state": { "name": "Arizona", "abbrev": "AZ" } }'); + }); + +}); diff --git a/modules/@angular/examples/forms/ts/reactiveSelectControl/module.ts b/modules/@angular/examples/forms/ts/reactiveSelectControl/module.ts new file mode 100644 index 0000000000..47c53b3fe7 --- /dev/null +++ b/modules/@angular/examples/forms/ts/reactiveSelectControl/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 {ReactiveSelectComp} from './reactive_select_control_example'; + +@NgModule({ + imports: [BrowserModule, ReactiveFormsModule], + declarations: [ReactiveSelectComp], + bootstrap: [ReactiveSelectComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/reactiveSelectControl/reactive_select_control_example.ts b/modules/@angular/examples/forms/ts/reactiveSelectControl/reactive_select_control_example.ts new file mode 100644 index 0000000000..d8a080fa8a --- /dev/null +++ b/modules/@angular/examples/forms/ts/reactiveSelectControl/reactive_select_control_example.ts @@ -0,0 +1,41 @@ +/** + * @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} from '@angular/forms'; + +@Component({ + selector: 'example-app', + template: ` +
+ +
+ +

Form value: {{ form.value | json }}

+ + `, +}) +export class ReactiveSelectComp { + states = [ + {name: 'Arizona', abbrev: 'AZ'}, + {name: 'California', abbrev: 'CA'}, + {name: 'Colorado', abbrev: 'CO'}, + {name: 'New York', abbrev: 'NY'}, + {name: 'Pennsylvania', abbrev: 'PA'}, + ]; + + form = new FormGroup({ + state: new FormControl(this.states[3]), + }); +} +// #enddocregion diff --git a/modules/@angular/examples/forms/ts/selectControl/e2e_test/select_control_spec.ts b/modules/@angular/examples/forms/ts/selectControl/e2e_test/select_control_spec.ts new file mode 100644 index 0000000000..a33b2d163e --- /dev/null +++ b/modules/@angular/examples/forms/ts/selectControl/e2e_test/select_control_spec.ts @@ -0,0 +1,34 @@ +/** + * @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('selectControl example', () => { + afterEach(verifyNoBrowserErrors); + let select: protractor.ElementFinder; + let options: protractor.ElementArrayFinder; + let p: protractor.ElementFinder; + + beforeEach(() => { + browser.get('/forms/ts/selectControl/index.html'); + select = element(by.css('select')); + options = element.all(by.css('option')); + p = element(by.css('p')); + }); + + it('should initially select the placeholder option', + () => { expect(options.get(0).getAttribute('selected')).toBe('true'); }); + + it('should update the model when the value changes in the UI', () => { + select.click(); + options.get(1).click(); + + expect(p.getText()).toEqual('Form value: { "state": { "name": "Arizona", "abbrev": "AZ" } }'); + }); + +}); diff --git a/modules/@angular/examples/forms/ts/selectControl/module.ts b/modules/@angular/examples/forms/ts/selectControl/module.ts new file mode 100644 index 0000000000..f2adc86335 --- /dev/null +++ b/modules/@angular/examples/forms/ts/selectControl/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 {FormsModule} from '@angular/forms'; +import {BrowserModule} from '@angular/platform-browser'; +import {SelectControlComp} from './select_control_example'; + +@NgModule({ + imports: [BrowserModule, FormsModule], + declarations: [SelectControlComp], + bootstrap: [SelectControlComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/selectControl/select_control_example.ts b/modules/@angular/examples/forms/ts/selectControl/select_control_example.ts new file mode 100644 index 0000000000..a1527bc973 --- /dev/null +++ b/modules/@angular/examples/forms/ts/selectControl/select_control_example.ts @@ -0,0 +1,37 @@ +/** + * @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'; + +@Component({ + selector: 'example-app', + template: ` +
+ +
+ +

Form value: {{ f.value | json }}

+ + `, +}) +export class SelectControlComp { + states = [ + {name: 'Arizona', abbrev: 'AZ'}, + {name: 'California', abbrev: 'CA'}, + {name: 'Colorado', abbrev: 'CO'}, + {name: 'New York', abbrev: 'NY'}, + {name: 'Pennsylvania', abbrev: 'PA'}, + ]; +} +// #enddocregion diff --git a/modules/@angular/forms/src/directives/ng_model.ts b/modules/@angular/forms/src/directives/ng_model.ts index 3296bf40e7..5e32a25d77 100644 --- a/modules/@angular/forms/src/directives/ng_model.ts +++ b/modules/@angular/forms/src/directives/ng_model.ts @@ -76,6 +76,11 @@ const resolvedPromise = Promise.resolve(null); * * {@example forms/ts/simpleForm/simple_form_example.ts region='Component'} * + * To see `ngModel` examples with different form control types, see: + * + * * Radio buttons: {@link RadioControlValueAccessor} + * * Selects: {@link SelectControlValueAccessor} + * * **npm package**: `@angular/forms` * * **NgModule**: `FormsModule` diff --git a/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts b/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts index 6e22c45ecc..d6bcacfa3d 100644 --- a/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts +++ b/modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts @@ -67,9 +67,14 @@ export const controlNameBinding: any = { * * {@example forms/ts/simpleFormGroup/simple_form_group_example.ts region='Component'} * - * * **npm package**: `@angular/forms` + * To see `formControlName` examples with different form control types, see: * - * * **NgModule**: {@link ReactiveFormsModule} + * * Radio buttons: {@link RadioControlValueAccessor} + * * Selects: {@link SelectControlValueAccessor} + * + * **npm package**: `@angular/forms` + * + * **NgModule**: {@link ReactiveFormsModule} * * @stable */ diff --git a/modules/@angular/forms/src/directives/select_control_value_accessor.ts b/modules/@angular/forms/src/directives/select_control_value_accessor.ts index 043c88ce89..d0d095db03 100644 --- a/modules/@angular/forms/src/directives/select_control_value_accessor.ts +++ b/modules/@angular/forms/src/directives/select_control_value_accessor.ts @@ -30,13 +30,41 @@ function _extractId(valueString: string): string { } /** - * The accessor for writing a value and listening to changes on a select element. + * @whatItDoes Writes values and listens to changes on a select element. * - * Note: We have to listen to the 'change' event because 'input' events aren't fired + * Used by {@link NgModel}, {@link FormControlDirective}, and {@link FormControlName} + * to keep the view synced with the {@link FormControl} model. + * + * @howToUse + * + * If you have imported the {@link FormsModule} or the {@link ReactiveFormsModule}, this + * value accessor will be active on any select control that has a form directive. You do + * **not** need to add a special selector to activate it. + * + * ### How to use select controls with form directives + * + * To use a select in a template-driven form, simply add an `ngModel` and a `name` + * attribute to the main `` tag. Like in the former example, you have the + * choice of binding to the `value` or `ngValue` property on the select's options. + * + * {@example forms/ts/reactiveSelectControl/reactive_select_control_example.ts region='Component'} + * + * Note: We listen to the 'change' event because 'input' events aren't fired * for selects in Firefox and IE: * https://bugzilla.mozilla.org/show_bug.cgi?id=1024350 * https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/ * + * * **npm package**: `@angular/forms` + * * @stable */ @Directive({ @@ -94,15 +122,11 @@ export class SelectControlValueAccessor implements ControlValueAccessor { } /** - * Marks `