From e8a156606552806c7406cc29381259e18d1bc83e Mon Sep 17 00:00:00 2001 From: Kara Date: Mon, 29 Aug 2016 17:49:42 -0700 Subject: [PATCH] fix(forms): support radio buttons with same name but diff parent (#11152) Closes #10065 --- .../forms/src/directives/ng_control.ts | 3 ++ .../@angular/forms/src/directives/ng_model.ts | 3 +- .../radio_control_value_accessor.ts | 2 +- .../reactive_directives/form_control_name.ts | 3 +- .../forms/test/reactive_integration_spec.ts | 43 +++++++++++++++++++ tools/public_api_guard/forms/index.d.ts | 4 +- 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/modules/@angular/forms/src/directives/ng_control.ts b/modules/@angular/forms/src/directives/ng_control.ts index 0e42fe1d3c..55cc00a342 100644 --- a/modules/@angular/forms/src/directives/ng_control.ts +++ b/modules/@angular/forms/src/directives/ng_control.ts @@ -8,6 +8,7 @@ import {AbstractControlDirective} from './abstract_control_directive'; +import {ControlContainer} from './control_container'; import {ControlValueAccessor} from './control_value_accessor'; import {AsyncValidatorFn, Validator, ValidatorFn} from './validators'; @@ -24,6 +25,8 @@ function unimplemented(): any { * @stable */ export abstract class NgControl extends AbstractControlDirective { + /** @internal */ + _parent: ControlContainer = null; name: string = null; valueAccessor: ControlValueAccessor = null; /** @internal */ diff --git a/modules/@angular/forms/src/directives/ng_model.ts b/modules/@angular/forms/src/directives/ng_model.ts index 5e1d23db63..fab5a90abb 100644 --- a/modules/@angular/forms/src/directives/ng_model.ts +++ b/modules/@angular/forms/src/directives/ng_model.ts @@ -71,12 +71,13 @@ export class NgModel extends NgControl implements OnChanges, @Output('ngModelChange') update = new EventEmitter(); - constructor(@Optional() @Host() private _parent: ControlContainer, + constructor(@Optional() @Host() parent: ControlContainer, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); + this._parent = parent; this._rawValidators = validators || []; this._rawAsyncValidators = asyncValidators || []; this.valueAccessor = selectValueAccessor(this, valueAccessors); 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 98a2dc4280..a44ac76533 100644 --- a/modules/@angular/forms/src/directives/radio_control_value_accessor.ts +++ b/modules/@angular/forms/src/directives/radio_control_value_accessor.ts @@ -53,7 +53,7 @@ export class RadioControlRegistry { controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor): boolean { if (!controlPair[0].control) return false; - return controlPair[0].control.root === accessor._control.control.root && + return controlPair[0]._parent === accessor._control._parent && controlPair[1].name === accessor.name; } } 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 1569c5ebd1..25ed3e9fa8 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 @@ -109,12 +109,13 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy { set isDisabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); } constructor( - @Optional() @Host() @SkipSelf() private _parent: ControlContainer, + @Optional() @Host() @SkipSelf() parent: ControlContainer, @Optional() @Self() @Inject(NG_VALIDATORS) validators: Array, @Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: Array, @Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); + this._parent = parent; this._rawValidators = validators || []; this._rawAsyncValidators = asyncValidators || []; this.valueAccessor = selectValueAccessor(this, valueAccessors); diff --git a/modules/@angular/forms/test/reactive_integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts index 3b6f9f435c..d7fcef0b10 100644 --- a/modules/@angular/forms/test/reactive_integration_spec.ts +++ b/modules/@angular/forms/test/reactive_integration_spec.ts @@ -856,6 +856,49 @@ export function main() { expect(form.value).toEqual({drink: 'sprite'}); }); + it('should differentiate controls on different levels with the same name', () => { + TestBed.overrideComponent(FormControlRadioButtons, { + set: { + template: ` +
+ + +
+ + +
+
+ ` + } + }); + const fixture = TestBed.createComponent(FormControlRadioButtons); + const form = new FormGroup({ + food: new FormControl('fish'), + nested: new FormGroup({food: new FormControl('fish')}) + }); + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + // model -> view + 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'); + fixture.detectChanges(); + + // view -> model + expect(form.get('food').value).toEqual('chicken'); + expect(form.get('nested.food').value).toEqual('fish'); + + expect(inputs[1].nativeElement.checked).toEqual(false); + expect(inputs[2].nativeElement.checked).toEqual(false); + expect(inputs[3].nativeElement.checked).toEqual(true); + + }); + }); describe('custom value accessors', () => { diff --git a/tools/public_api_guard/forms/index.d.ts b/tools/public_api_guard/forms/index.d.ts index b3fc571fdf..c27a3a36ed 100644 --- a/tools/public_api_guard/forms/index.d.ts +++ b/tools/public_api_guard/forms/index.d.ts @@ -245,7 +245,7 @@ export declare class FormControlName extends NgControl implements OnChanges, OnD path: string[]; update: EventEmitter<{}>; validator: ValidatorFn; - constructor(_parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); + constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; viewToModelUpdate(newValue: any): void; @@ -406,7 +406,7 @@ export declare class NgModel extends NgControl implements OnChanges, OnDestroy { update: EventEmitter<{}>; validator: ValidatorFn; viewModel: any; - constructor(_parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); + constructor(parent: ControlContainer, validators: Array, asyncValidators: Array, valueAccessors: ControlValueAccessor[]); ngOnChanges(changes: SimpleChanges): void; ngOnDestroy(): void; viewToModelUpdate(newValue: any): void;