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 {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer, forwardRef} from '@angular/core';
|
||||||
|
|
||||||
import {ListWrapper} from '../facade/collection';
|
import {ListWrapper} from '../facade/collection';
|
||||||
|
import {BaseException} from '../facade/exceptions';
|
||||||
import {isPresent} from '../facade/lang';
|
import {isPresent} from '../facade/lang';
|
||||||
|
|
||||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
|
||||||
@ -50,7 +51,8 @@ export class RadioControlRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _isSameGroup(
|
private _isSameGroup(
|
||||||
controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor) {
|
controlPair: [NgControl, RadioControlValueAccessor],
|
||||||
|
accessor: RadioControlValueAccessor): boolean {
|
||||||
if (!controlPair[0].control) return false;
|
if (!controlPair[0].control) return false;
|
||||||
return controlPair[0].control.root === accessor._control.control.root &&
|
return controlPair[0].control.root === accessor._control.control.root &&
|
||||||
controlPair[1].name === accessor.name;
|
controlPair[1].name === accessor.name;
|
||||||
@ -92,6 +94,7 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
|||||||
onTouched = () => {}
|
onTouched = () => {}
|
||||||
|
|
||||||
@Input() name: string;
|
@Input() name: string;
|
||||||
|
@Input() formControlName: string;
|
||||||
@Input() value: any;
|
@Input() value: any;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -100,6 +103,7 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this._control = this._injector.get(NgControl);
|
this._control = this._injector.get(NgControl);
|
||||||
|
this._checkName();
|
||||||
this._registry.add(this._control, this);
|
this._registry.add(this._control, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,4 +127,18 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
|||||||
fireUncheck(value: any): void { this.writeValue(value); }
|
fireUncheck(value: any): void { this.writeValue(value); }
|
||||||
|
|
||||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
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>',
|
it('should support removing controls from <type=radio>',
|
||||||
inject(
|
inject(
|
||||||
[TestComponentBuilder, AsyncTestCompleter],
|
[TestComponentBuilder, AsyncTestCompleter],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user