feat(forms): use formControlName on radio buttons when name is absent (#9681)
This commit is contained in:
parent
9340e1b065
commit
0961bd1eff
|
@ -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">
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in New Issue