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 6e421e2c2f..74d62d3415 100644 --- a/modules/@angular/forms/src/directives/radio_control_value_accessor.ts +++ b/modules/@angular/forms/src/directives/radio_control_value_accessor.ts @@ -9,6 +9,7 @@ import {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer, forwardRef} from '@angular/core'; import {ListWrapper} from '../facade/collection'; +import {BaseException} from '../facade/exceptions'; import {isPresent} from '../facade/lang'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; @@ -50,7 +51,8 @@ export class RadioControlRegistry { } private _isSameGroup( - controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor) { + controlPair: [NgControl, RadioControlValueAccessor], + accessor: RadioControlValueAccessor): boolean { if (!controlPair[0].control) return false; return controlPair[0].control.root === accessor._control.control.root && controlPair[1].name === accessor.name; @@ -92,6 +94,7 @@ export class RadioControlValueAccessor implements ControlValueAccessor, onTouched = () => {} @Input() name: string; + @Input() formControlName: string; @Input() value: any; constructor( @@ -100,6 +103,7 @@ export class RadioControlValueAccessor implements ControlValueAccessor, ngOnInit(): void { this._control = this._injector.get(NgControl); + this._checkName(); this._registry.add(this._control, this); } @@ -123,4 +127,18 @@ export class RadioControlValueAccessor implements ControlValueAccessor, fireUncheck(value: any): void { this.writeValue(value); } registerOnTouched(fn: () => {}): void { this.onTouched = fn; } + + private _checkName(): void { + if (this.name && this.formControlName && this.name !== this.formControlName) { + this._throwNameError(); + } + if (!this.name && this.formControlName) this.name = this.formControlName; + } + + private _throwNameError(): void { + throw new BaseException(` + If you define both a name and a formControlName attribute on your radio button, their values + must match. Ex: + `); + } } diff --git a/modules/@angular/forms/test/integration_spec.ts b/modules/@angular/forms/test/integration_spec.ts index 90ac1a2f32..bf7bb88ee7 100644 --- a/modules/@angular/forms/test/integration_spec.ts +++ b/modules/@angular/forms/test/integration_spec.ts @@ -615,6 +615,76 @@ export function main() { }); })); + it('should use formControlName to group radio buttons when name is absent', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
+ + + + +
`; + + const foodCtrl = new FormControl('fish'); + const drinkCtrl = new FormControl('sprite'); + tcb.overrideTemplate(MyComp8, t) + .overrideProviders(MyComp8, providerArr) + .createAsync(MyComp8) + .then((fixture) => { + fixture.debugElement.componentInstance.form = + new FormGroup({'food': foodCtrl, 'drink': drinkCtrl}); + fixture.detectChanges(); + + const inputs = fixture.debugElement.queryAll(By.css('input')); + expect(inputs[0].nativeElement.checked).toEqual(false); + expect(inputs[1].nativeElement.checked).toEqual(true); + expect(inputs[2].nativeElement.checked).toEqual(false); + expect(inputs[3].nativeElement.checked).toEqual(true); + + dispatchEvent(inputs[0].nativeElement, 'change'); + inputs[0].nativeElement.checked = true; + fixture.detectChanges(); + + const value = fixture.debugElement.componentInstance.form.value; + expect(value.food).toEqual('chicken'); + expect(inputs[1].nativeElement.checked).toEqual(false); + expect(inputs[2].nativeElement.checked).toEqual(false); + expect(inputs[3].nativeElement.checked).toEqual(true); + + drinkCtrl.updateValue('cola'); + fixture.detectChanges(); + + expect(inputs[0].nativeElement.checked).toEqual(true); + expect(inputs[1].nativeElement.checked).toEqual(false); + expect(inputs[2].nativeElement.checked).toEqual(true); + expect(inputs[3].nativeElement.checked).toEqual(false); + + async.done(); + }); + })); + + it('should throw if radio button name does not match formControlName attr', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t) + .overrideProviders(MyComp8, providerArr) + .createAsync(MyComp8) + .then((fixture) => { + fixture.debugElement.componentInstance.form = + new FormGroup({'food': new FormControl('fish')}); + expect(() => fixture.detectChanges()) + .toThrowError( + new RegExp('If you define both a name and a formControlName')); + async.done(); + }); + })); + it('should support removing controls from ', inject( [TestComponentBuilder, AsyncTestCompleter],