feat(forms): use formControlName on radio buttons when name is absent (#9681)

This commit is contained in:
Kara 2016-06-28 15:21:53 -06:00 committed by GitHub
parent 9340e1b065
commit 0961bd1eff
2 changed files with 89 additions and 1 deletions

View File

@ -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: <input type="radio" formControlName="food" name="food">
`);
}
}

View File

@ -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 = `<form [formGroup]="form">
<input type="radio" formControlName="food" value="chicken">
<input type="radio" formControlName="food" value="fish">
<input type="radio" formControlName="drink" value="cola">
<input type="radio" formControlName="drink" value="sprite">
</form>`;
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 = `<form [formGroup]="form">
<input type="radio" formControlName="food" name="drink" value="chicken">
</form>`;
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 <type=radio>',
inject(
[TestComponentBuilder, AsyncTestCompleter],