diff --git a/modules/@angular/examples/forms/ts/radioButtons/e2e_test/radio_button_spec.ts b/modules/@angular/examples/forms/ts/radioButtons/e2e_test/radio_button_spec.ts new file mode 100644 index 0000000000..6bba72e03b --- /dev/null +++ b/modules/@angular/examples/forms/ts/radioButtons/e2e_test/radio_button_spec.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 + */ + +import {verifyNoBrowserErrors} from '../../../../_common/e2e_util'; + +describe('radioButtons example', () => { + afterEach(verifyNoBrowserErrors); + let inputs: protractor.ElementArrayFinder; + let paragraphs: protractor.ElementArrayFinder; + + beforeEach(() => { + browser.get('/forms/ts/radioButtons/index.html'); + inputs = element.all(by.css('input')); + paragraphs = element.all(by.css('p')); + }); + + it('should populate the UI with initial values', () => { + expect(inputs.get(0).getAttribute('checked')).toEqual(null); + expect(inputs.get(1).getAttribute('checked')).toEqual('true'); + expect(inputs.get(2).getAttribute('checked')).toEqual(null); + + expect(paragraphs.get(0).getText()).toEqual('Form value: { "food": "lamb" }'); + expect(paragraphs.get(1).getText()).toEqual('myFood value: lamb'); + }); + + it('update model and other buttons as the UI value changes', () => { + inputs.get(0).click(); + + expect(inputs.get(0).getAttribute('checked')).toEqual('true'); + expect(inputs.get(1).getAttribute('checked')).toEqual(null); + expect(inputs.get(2).getAttribute('checked')).toEqual(null); + + expect(paragraphs.get(0).getText()).toEqual('Form value: { "food": "beef" }'); + expect(paragraphs.get(1).getText()).toEqual('myFood value: beef'); + }); +}); diff --git a/modules/@angular/examples/forms/ts/radioButtons/module.ts b/modules/@angular/examples/forms/ts/radioButtons/module.ts new file mode 100644 index 0000000000..6a8151e88e --- /dev/null +++ b/modules/@angular/examples/forms/ts/radioButtons/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 {RadioButtonComp} from './radio_button_example'; + +@NgModule({ + imports: [BrowserModule, FormsModule], + declarations: [RadioButtonComp], + bootstrap: [RadioButtonComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/radioButtons/radio_button_example.ts b/modules/@angular/examples/forms/ts/radioButtons/radio_button_example.ts new file mode 100644 index 0000000000..d5a331b3a1 --- /dev/null +++ b/modules/@angular/examples/forms/ts/radioButtons/radio_button_example.ts @@ -0,0 +1,28 @@ +/** + * @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 TemplateDriven +import {Component} from '@angular/core'; + +@Component({ + selector: 'example-app', + template: ` +
+ Beef + Lamb + Fish +
+ +

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

+

myFood value: {{ myFood }}

+ `, +}) +export class RadioButtonComp { + myFood = 'lamb'; +} +// #enddocregion diff --git a/modules/@angular/examples/forms/ts/reactiveRadioButtons/e2e_test/reactive_radio_button_spec.ts b/modules/@angular/examples/forms/ts/reactiveRadioButtons/e2e_test/reactive_radio_button_spec.ts new file mode 100644 index 0000000000..93f8e687da --- /dev/null +++ b/modules/@angular/examples/forms/ts/reactiveRadioButtons/e2e_test/reactive_radio_button_spec.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 + */ + +import {verifyNoBrowserErrors} from '../../../../_common/e2e_util'; + +describe('radioButtons example', () => { + afterEach(verifyNoBrowserErrors); + let inputs: protractor.ElementArrayFinder; + + beforeEach(() => { + browser.get('/forms/ts/reactiveRadioButtons/index.html'); + inputs = element.all(by.css('input')); + }); + + it('should populate the UI with initial values', () => { + expect(inputs.get(0).getAttribute('checked')).toEqual(null); + expect(inputs.get(1).getAttribute('checked')).toEqual('true'); + expect(inputs.get(2).getAttribute('checked')).toEqual(null); + + expect(element(by.css('p')).getText()).toEqual('Form value: { "food": "lamb" }'); + }); + + it('update model and other buttons as the UI value changes', () => { + inputs.get(0).click(); + + expect(inputs.get(0).getAttribute('checked')).toEqual('true'); + expect(inputs.get(1).getAttribute('checked')).toEqual(null); + expect(inputs.get(2).getAttribute('checked')).toEqual(null); + + expect(element(by.css('p')).getText()).toEqual('Form value: { "food": "beef" }'); + }); +}); diff --git a/modules/@angular/examples/forms/ts/reactiveRadioButtons/module.ts b/modules/@angular/examples/forms/ts/reactiveRadioButtons/module.ts new file mode 100644 index 0000000000..17f67b430b --- /dev/null +++ b/modules/@angular/examples/forms/ts/reactiveRadioButtons/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 {ReactiveRadioButtonComp} from './reactive_radio_button_example'; + +@NgModule({ + imports: [BrowserModule, ReactiveFormsModule], + declarations: [ReactiveRadioButtonComp], + bootstrap: [ReactiveRadioButtonComp] +}) +export class AppModule { +} diff --git a/modules/@angular/examples/forms/ts/reactiveRadioButtons/reactive_radio_button_example.ts b/modules/@angular/examples/forms/ts/reactiveRadioButtons/reactive_radio_button_example.ts new file mode 100644 index 0000000000..f227acf303 --- /dev/null +++ b/modules/@angular/examples/forms/ts/reactiveRadioButtons/reactive_radio_button_example.ts @@ -0,0 +1,30 @@ +/** + * @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 Reactive +import {Component} from '@angular/core'; +import {FormControl, FormGroup} from '@angular/forms'; + +@Component({ + selector: 'example-app', + template: ` +
+ Beef + Lamb + Fish +
+ +

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

+ `, +}) +export class ReactiveRadioButtonComp { + form = new FormGroup({ + food: new FormControl('lamb'), + }); +} +// #enddocregion diff --git a/modules/@angular/forms/src/directives/radio_control_value_accessor.ts b/modules/@angular/forms/src/directives/radio_control_value_accessor.ts index afc7e34fec..db27685943 100644 --- a/modules/@angular/forms/src/directives/radio_control_value_accessor.ts +++ b/modules/@angular/forms/src/directives/radio_control_value_accessor.ts @@ -59,21 +59,33 @@ export class RadioControlRegistry { } /** - * The accessor for writing a radio control value and listening to changes that is used by the - * {@link NgModel}, {@link FormControlDirective}, and {@link FormControlName} directives. + * @whatItDoes Writes radio control values and listens to radio control changes. * - * ### Example - * ``` - * @Component({ - * template: ` - * - * - * ` - * }) - * class FoodCmp { - * food = 'chicken'; - * } - * ``` + * 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 radio control that has a form directive. You do + * **not** need to add a special selector to activate it. + * + * ### How to use radio buttons with form directives + * + * To use radio buttons in a template-driven form, you'll want to ensure that radio buttons + * in the same group have the same `name` attribute. Radio buttons with different `name` + * attributes do not affect each other. + * + * {@example forms/ts/radioButtons/radio_button_example.ts region='TemplateDriven'} + * + * When using radio buttons in a reactive form, radio buttons in the same group should have the + * same `formControlName`. You can also add a `name` attribute, but it's optional. + * + * {@example forms/ts/reactiveRadioButtons/reactive_radio_button_example.ts region='Reactive'} + * + * * **npm package**: `@angular/forms` + * + * @stable */ @Directive({ selector: diff --git a/modules/@angular/forms/src/forms.ts b/modules/@angular/forms/src/forms.ts index 580ca1971b..1887c26242 100644 --- a/modules/@angular/forms/src/forms.ts +++ b/modules/@angular/forms/src/forms.ts @@ -10,11 +10,8 @@ * @module * @description * This module is used for handling user input, by defining and building a {@link FormGroup} that - * consists of - * {@link FormControl} objects, and mapping them onto the DOM. {@link FormControl} objects can then - * be used - * to read information - * from the form DOM elements. + * consists of {@link FormControl} objects, and mapping them onto the DOM. {@link FormControl} + * objects can then be used to read information from the form DOM elements. * * Forms providers are not included in default providers; you must import these providers * explicitly. @@ -33,6 +30,7 @@ export {NgControlStatus, NgControlStatusGroup} from './directives/ng_control_sta export {NgForm} from './directives/ng_form'; export {NgModel} from './directives/ng_model'; export {NgModelGroup} from './directives/ng_model_group'; +export {RadioControlValueAccessor} from './directives/radio_control_value_accessor'; export {FormControlDirective} from './directives/reactive_directives/form_control_directive'; export {FormControlName} from './directives/reactive_directives/form_control_name'; export {FormGroupDirective} from './directives/reactive_directives/form_group_directive'; diff --git a/tools/public_api_guard/forms/index.d.ts b/tools/public_api_guard/forms/index.d.ts index 4224dddc07..ca108f8610 100644 --- a/tools/public_api_guard/forms/index.d.ts +++ b/tools/public_api_guard/forms/index.d.ts @@ -439,6 +439,23 @@ export declare class PatternValidator implements Validator, OnChanges { }; } +/** @stable */ +export declare class RadioControlValueAccessor implements ControlValueAccessor, OnDestroy, OnInit { + formControlName: string; + name: string; + onChange: () => void; + onTouched: () => void; + value: any; + constructor(_renderer: Renderer, _elementRef: ElementRef, _registry: RadioControlRegistry, _injector: Injector); + fireUncheck(value: any): void; + ngOnDestroy(): void; + ngOnInit(): void; + registerOnChange(fn: (_: any) => {}): void; + registerOnTouched(fn: () => {}): void; + setDisabledState(isDisabled: boolean): void; + writeValue(value: any): void; +} + /** @stable */ export declare class ReactiveFormsModule { }