fix(forms): support rebinding nested controls (#11210)
This commit is contained in:
parent
d309f7799c
commit
8c09933803
|
@ -96,9 +96,11 @@ export const controlNameBinding: any = {
|
||||||
*/
|
*/
|
||||||
@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
|
@Directive({selector: '[formControlName]', providers: [controlNameBinding]})
|
||||||
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
|
private _added = false;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
viewModel: any;
|
viewModel: any;
|
||||||
private _added = false;
|
/** @internal */
|
||||||
|
_control: FormControl;
|
||||||
|
|
||||||
@Input('formControlName') name: string;
|
@Input('formControlName') name: string;
|
||||||
|
|
||||||
|
@ -122,12 +124,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: SimpleChanges) {
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
if (!this._added) {
|
if (!this._added) this._setUpControl();
|
||||||
this._checkParentType();
|
|
||||||
this.formDirective.addControl(this);
|
|
||||||
if (this.control.disabled) this.valueAccessor.setDisabledState(true);
|
|
||||||
this._added = true;
|
|
||||||
}
|
|
||||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||||
this.viewModel = this.model;
|
this.viewModel = this.model;
|
||||||
this.formDirective.updateModel(this, this.model);
|
this.formDirective.updateModel(this, this.model);
|
||||||
|
@ -155,7 +152,7 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
return composeAsyncValidators(this._rawAsyncValidators);
|
return composeAsyncValidators(this._rawAsyncValidators);
|
||||||
}
|
}
|
||||||
|
|
||||||
get control(): FormControl { return this.formDirective.getControl(this); }
|
get control(): FormControl { return this._control; }
|
||||||
|
|
||||||
private _checkParentType(): void {
|
private _checkParentType(): void {
|
||||||
if (!(this._parent instanceof FormGroupName) &&
|
if (!(this._parent instanceof FormGroupName) &&
|
||||||
|
@ -167,4 +164,11 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||||
ReactiveErrors.controlParentException();
|
ReactiveErrors.controlParentException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _setUpControl() {
|
||||||
|
this._checkParentType();
|
||||||
|
this._control = this.formDirective.addControl(this);
|
||||||
|
if (this.control.disabled) this.valueAccessor.setDisabledState(true);
|
||||||
|
this._added = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,10 @@ import {FormArray, FormControl, FormGroup} from '../../model';
|
||||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../../validators';
|
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../../validators';
|
||||||
import {ControlContainer} from '../control_container';
|
import {ControlContainer} from '../control_container';
|
||||||
import {Form} from '../form_interface';
|
import {Form} from '../form_interface';
|
||||||
import {NgControl} from '../ng_control';
|
|
||||||
import {ReactiveErrors} from '../reactive_errors';
|
import {ReactiveErrors} from '../reactive_errors';
|
||||||
import {cleanUpControl, composeAsyncValidators, composeValidators, setUpControl, setUpFormContainer} from '../shared';
|
import {cleanUpControl, composeAsyncValidators, composeValidators, setUpControl, setUpFormContainer} from '../shared';
|
||||||
|
|
||||||
|
import {FormControlName} from './form_control_name';
|
||||||
import {FormArrayName, FormGroupName} from './form_group_name';
|
import {FormArrayName, FormGroupName} from './form_group_name';
|
||||||
|
|
||||||
export const formDirectiveProvider: any = {
|
export const formDirectiveProvider: any = {
|
||||||
|
@ -105,7 +105,8 @@ export const formDirectiveProvider: any = {
|
||||||
export class FormGroupDirective extends ControlContainer implements Form,
|
export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
OnChanges {
|
OnChanges {
|
||||||
private _submitted: boolean = false;
|
private _submitted: boolean = false;
|
||||||
directives: NgControl[] = [];
|
private _oldForm: FormGroup;
|
||||||
|
directives: FormControlName[] = [];
|
||||||
|
|
||||||
@Input('formGroup') form: FormGroup = null;
|
@Input('formGroup') form: FormGroup = null;
|
||||||
@Output() ngSubmit = new EventEmitter();
|
@Output() ngSubmit = new EventEmitter();
|
||||||
|
@ -119,12 +120,9 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this._checkFormPresent();
|
this._checkFormPresent();
|
||||||
if (StringMapWrapper.contains(changes, 'form')) {
|
if (StringMapWrapper.contains(changes, 'form')) {
|
||||||
var sync = composeValidators(this._validators);
|
this._updateValidators();
|
||||||
this.form.validator = Validators.compose([this.form.validator, sync]);
|
this._updateDomValue();
|
||||||
|
this._updateRegistrations();
|
||||||
var async = composeAsyncValidators(this._asyncValidators);
|
|
||||||
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]);
|
|
||||||
this._updateDomValue(changes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,16 +134,17 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
|
|
||||||
get path(): string[] { return []; }
|
get path(): string[] { return []; }
|
||||||
|
|
||||||
addControl(dir: NgControl): void {
|
addControl(dir: FormControlName): FormControl {
|
||||||
const ctrl: any = this.form.get(dir.path);
|
const ctrl: any = this.form.get(dir.path);
|
||||||
setUpControl(ctrl, dir);
|
setUpControl(ctrl, dir);
|
||||||
ctrl.updateValueAndValidity({emitEvent: false});
|
ctrl.updateValueAndValidity({emitEvent: false});
|
||||||
this.directives.push(dir);
|
this.directives.push(dir);
|
||||||
|
return ctrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
getControl(dir: NgControl): FormControl { return <FormControl>this.form.get(dir.path); }
|
getControl(dir: FormControlName): FormControl { return <FormControl>this.form.get(dir.path); }
|
||||||
|
|
||||||
removeControl(dir: NgControl): void { ListWrapper.remove(this.directives, dir); }
|
removeControl(dir: FormControlName): void { ListWrapper.remove(this.directives, dir); }
|
||||||
|
|
||||||
addFormGroup(dir: FormGroupName): void {
|
addFormGroup(dir: FormGroupName): void {
|
||||||
var ctrl: any = this.form.get(dir.path);
|
var ctrl: any = this.form.get(dir.path);
|
||||||
|
@ -167,7 +166,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
|
|
||||||
getFormArray(dir: FormArrayName): FormArray { return <FormArray>this.form.get(dir.path); }
|
getFormArray(dir: FormArrayName): FormArray { return <FormArray>this.form.get(dir.path); }
|
||||||
|
|
||||||
updateModel(dir: NgControl, value: any): void {
|
updateModel(dir: FormControlName, value: any): void {
|
||||||
var ctrl = <FormControl>this.form.get(dir.path);
|
var ctrl = <FormControl>this.form.get(dir.path);
|
||||||
ctrl.setValue(value);
|
ctrl.setValue(value);
|
||||||
}
|
}
|
||||||
|
@ -186,21 +185,33 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_updateDomValue(changes: SimpleChanges) {
|
_updateDomValue() {
|
||||||
const oldForm = changes['form'].previousValue;
|
|
||||||
|
|
||||||
this.directives.forEach(dir => {
|
this.directives.forEach(dir => {
|
||||||
const newCtrl: any = this.form.get(dir.path);
|
const newCtrl: any = this.form.get(dir.path);
|
||||||
const oldCtrl = oldForm.get(dir.path);
|
if (dir._control !== newCtrl) {
|
||||||
if (oldCtrl !== newCtrl) {
|
cleanUpControl(dir._control, dir);
|
||||||
cleanUpControl(oldCtrl, dir);
|
|
||||||
if (newCtrl) setUpControl(newCtrl, dir);
|
if (newCtrl) setUpControl(newCtrl, dir);
|
||||||
|
dir._control = newCtrl;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.form._updateTreeValidity({emitEvent: false});
|
this.form._updateTreeValidity({emitEvent: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _updateRegistrations() {
|
||||||
|
this.form._registerOnCollectionChange(() => this._updateDomValue());
|
||||||
|
if (this._oldForm) this._oldForm._registerOnCollectionChange(() => {});
|
||||||
|
this._oldForm = this.form;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateValidators() {
|
||||||
|
const sync = composeValidators(this._validators);
|
||||||
|
this.form.validator = Validators.compose([this.form.validator, sync]);
|
||||||
|
|
||||||
|
const async = composeAsyncValidators(this._asyncValidators);
|
||||||
|
this.form.asyncValidator = Validators.composeAsync([this.form.asyncValidator, async]);
|
||||||
|
}
|
||||||
|
|
||||||
private _checkFormPresent() {
|
private _checkFormPresent() {
|
||||||
if (isBlank(this.form)) {
|
if (isBlank(this.form)) {
|
||||||
ReactiveErrors.missingFormException();
|
ReactiveErrors.missingFormException();
|
||||||
|
|
|
@ -81,6 +81,8 @@ function coerceToAsyncValidator(asyncValidator: AsyncValidatorFn | AsyncValidato
|
||||||
export abstract class AbstractControl {
|
export abstract class AbstractControl {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_value: any;
|
_value: any;
|
||||||
|
/** @internal */
|
||||||
|
_onCollectionChange = () => {};
|
||||||
|
|
||||||
private _valueChanges: EventEmitter<any>;
|
private _valueChanges: EventEmitter<any>;
|
||||||
private _statusChanges: EventEmitter<any>;
|
private _statusChanges: EventEmitter<any>;
|
||||||
|
@ -420,6 +422,9 @@ export abstract class AbstractControl {
|
||||||
return isStringMap(formState) && Object.keys(formState).length === 2 && 'value' in formState &&
|
return isStringMap(formState) && Object.keys(formState).length === 2 && 'value' in formState &&
|
||||||
'disabled' in formState;
|
'disabled' in formState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_registerOnCollectionChange(fn: () => void): void { this._onCollectionChange = fn; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -530,6 +535,7 @@ export class FormControl extends AbstractControl {
|
||||||
_clearChangeFns(): void {
|
_clearChangeFns(): void {
|
||||||
this._onChange = [];
|
this._onChange = [];
|
||||||
this._onDisabledChange = null;
|
this._onDisabledChange = null;
|
||||||
|
this._onCollectionChange = () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -574,7 +580,7 @@ export class FormGroup extends AbstractControl {
|
||||||
asyncValidator: AsyncValidatorFn = null) {
|
asyncValidator: AsyncValidatorFn = null) {
|
||||||
super(validator, asyncValidator);
|
super(validator, asyncValidator);
|
||||||
this._initObservables();
|
this._initObservables();
|
||||||
this._setParentForControls();
|
this._setUpControls();
|
||||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,6 +591,7 @@ export class FormGroup extends AbstractControl {
|
||||||
if (this.controls[name]) return this.controls[name];
|
if (this.controls[name]) return this.controls[name];
|
||||||
this.controls[name] = control;
|
this.controls[name] = control;
|
||||||
control.setParent(this);
|
control.setParent(this);
|
||||||
|
control._registerOnCollectionChange(this._onCollectionChange);
|
||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,14 +601,28 @@ export class FormGroup extends AbstractControl {
|
||||||
addControl(name: string, control: AbstractControl): void {
|
addControl(name: string, control: AbstractControl): void {
|
||||||
this.registerControl(name, control);
|
this.registerControl(name, control);
|
||||||
this.updateValueAndValidity();
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a control from this group.
|
* Remove a control from this group.
|
||||||
*/
|
*/
|
||||||
removeControl(name: string): void {
|
removeControl(name: string): void {
|
||||||
|
if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {});
|
||||||
StringMapWrapper.delete(this.controls, name);
|
StringMapWrapper.delete(this.controls, name);
|
||||||
this.updateValueAndValidity();
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace an existing control.
|
||||||
|
*/
|
||||||
|
setControl(name: string, control: AbstractControl): void {
|
||||||
|
if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {});
|
||||||
|
StringMapWrapper.delete(this.controls, name);
|
||||||
|
if (control) this.registerControl(name, control);
|
||||||
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -666,8 +687,11 @@ export class FormGroup extends AbstractControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_setParentForControls() {
|
_setUpControls() {
|
||||||
this._forEachChild((control: AbstractControl, name: string) => { control.setParent(this); });
|
this._forEachChild((control: AbstractControl) => {
|
||||||
|
control.setParent(this);
|
||||||
|
control._registerOnCollectionChange(this._onCollectionChange);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -750,7 +774,7 @@ export class FormArray extends AbstractControl {
|
||||||
asyncValidator: AsyncValidatorFn = null) {
|
asyncValidator: AsyncValidatorFn = null) {
|
||||||
super(validator, asyncValidator);
|
super(validator, asyncValidator);
|
||||||
this._initObservables();
|
this._initObservables();
|
||||||
this._setParentForControls();
|
this._setUpControls();
|
||||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,8 +788,9 @@ export class FormArray extends AbstractControl {
|
||||||
*/
|
*/
|
||||||
push(control: AbstractControl): void {
|
push(control: AbstractControl): void {
|
||||||
this.controls.push(control);
|
this.controls.push(control);
|
||||||
control.setParent(this);
|
this._registerControl(control);
|
||||||
this.updateValueAndValidity();
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -773,16 +798,35 @@ export class FormArray extends AbstractControl {
|
||||||
*/
|
*/
|
||||||
insert(index: number, control: AbstractControl): void {
|
insert(index: number, control: AbstractControl): void {
|
||||||
ListWrapper.insert(this.controls, index, control);
|
ListWrapper.insert(this.controls, index, control);
|
||||||
control.setParent(this);
|
this._registerControl(control);
|
||||||
this.updateValueAndValidity();
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the control at the given `index` in the array.
|
* Remove the control at the given `index` in the array.
|
||||||
*/
|
*/
|
||||||
removeAt(index: number): void {
|
removeAt(index: number): void {
|
||||||
|
if (this.controls[index]) this.controls[index]._registerOnCollectionChange(() => {});
|
||||||
ListWrapper.removeAt(this.controls, index);
|
ListWrapper.removeAt(this.controls, index);
|
||||||
this.updateValueAndValidity();
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace an existing control.
|
||||||
|
*/
|
||||||
|
setControl(index: number, control: AbstractControl): void {
|
||||||
|
if (this.controls[index]) this.controls[index]._registerOnCollectionChange(() => {});
|
||||||
|
ListWrapper.removeAt(this.controls, index);
|
||||||
|
|
||||||
|
if (control) {
|
||||||
|
ListWrapper.insert(this.controls, index, control);
|
||||||
|
this._registerControl(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateValueAndValidity();
|
||||||
|
this._onCollectionChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -849,8 +893,8 @@ export class FormArray extends AbstractControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_setParentForControls(): void {
|
_setUpControls(): void {
|
||||||
this._forEachChild((control: AbstractControl) => { control.setParent(this); });
|
this._forEachChild((control: AbstractControl) => this._registerControl(control));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
|
@ -869,4 +913,9 @@ export class FormArray extends AbstractControl {
|
||||||
}
|
}
|
||||||
return !!this.controls.length;
|
return !!this.controls.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _registerControl(control: AbstractControl) {
|
||||||
|
control.setParent(this);
|
||||||
|
control._registerOnCollectionChange(this._onCollectionChange);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -552,6 +552,7 @@ export function main() {
|
||||||
parent.form = new FormGroup({'name': formModel});
|
parent.form = new FormGroup({'name': formModel});
|
||||||
controlNameDir = new FormControlName(parent, [], [], [defaultAccessor]);
|
controlNameDir = new FormControlName(parent, [], [], [defaultAccessor]);
|
||||||
controlNameDir.name = 'name';
|
controlNameDir.name = 'name';
|
||||||
|
controlNameDir._control = formModel;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reexport control properties', () => {
|
it('should reexport control properties', () => {
|
||||||
|
|
|
@ -806,6 +806,49 @@ export function main() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setControl()', () => {
|
||||||
|
let c: FormControl;
|
||||||
|
let a: FormArray;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
c = new FormControl('one');
|
||||||
|
a = new FormArray([c]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace existing control with new control', () => {
|
||||||
|
const c2 = new FormControl('new!', Validators.minLength(10));
|
||||||
|
a.setControl(0, c2);
|
||||||
|
|
||||||
|
expect(a.controls[0]).toEqual(c2);
|
||||||
|
expect(a.value).toEqual(['new!']);
|
||||||
|
expect(a.valid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add control if control did not exist before', () => {
|
||||||
|
const c2 = new FormControl('new!', Validators.minLength(10));
|
||||||
|
a.setControl(1, c2);
|
||||||
|
|
||||||
|
expect(a.controls[1]).toEqual(c2);
|
||||||
|
expect(a.value).toEqual(['one', 'new!']);
|
||||||
|
expect(a.valid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove control if new control is null', () => {
|
||||||
|
a.setControl(0, null);
|
||||||
|
expect(a.controls[0]).not.toBeDefined();
|
||||||
|
expect(a.value).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only emit value change event once', () => {
|
||||||
|
const logger: string[] = [];
|
||||||
|
const c2 = new FormControl('new!');
|
||||||
|
a.valueChanges.subscribe(() => logger.push('change!'));
|
||||||
|
a.setControl(0, c2);
|
||||||
|
expect(logger).toEqual(['change!']);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -842,6 +842,49 @@ export function main() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('setControl()', () => {
|
||||||
|
let c: FormControl;
|
||||||
|
let g: FormGroup;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
c = new FormControl('one');
|
||||||
|
g = new FormGroup({one: c});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace existing control with new control', () => {
|
||||||
|
const c2 = new FormControl('new!', Validators.minLength(10));
|
||||||
|
g.setControl('one', c2);
|
||||||
|
|
||||||
|
expect(g.controls['one']).toEqual(c2);
|
||||||
|
expect(g.value).toEqual({one: 'new!'});
|
||||||
|
expect(g.valid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add control if control did not exist before', () => {
|
||||||
|
const c2 = new FormControl('new!', Validators.minLength(10));
|
||||||
|
g.setControl('two', c2);
|
||||||
|
|
||||||
|
expect(g.controls['two']).toEqual(c2);
|
||||||
|
expect(g.value).toEqual({one: 'one', two: 'new!'});
|
||||||
|
expect(g.valid).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove control if new control is null', () => {
|
||||||
|
g.setControl('one', null);
|
||||||
|
expect(g.controls['one']).not.toBeDefined();
|
||||||
|
expect(g.value).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only emit value change event once', () => {
|
||||||
|
const logger: string[] = [];
|
||||||
|
const c2 = new FormControl('new!');
|
||||||
|
g.valueChanges.subscribe(() => logger.push('change!'));
|
||||||
|
g.setControl('one', c2);
|
||||||
|
expect(logger).toEqual(['change!']);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,6 +256,91 @@ export function main() {
|
||||||
expect(inputs[2]).not.toBeDefined();
|
expect(inputs[2]).not.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('nested control rebinding', () => {
|
||||||
|
|
||||||
|
it('should attach dir to control when leaf control changes', () => {
|
||||||
|
const form = new FormGroup({'login': new FormControl('oldValue')});
|
||||||
|
const fixture = TestBed.createComponent(FormGroupComp);
|
||||||
|
fixture.debugElement.componentInstance.form = form;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
form.removeControl('login');
|
||||||
|
form.addControl('login', new FormControl('newValue'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
|
expect(input.nativeElement.value).toEqual('newValue');
|
||||||
|
|
||||||
|
input.nativeElement.value = 'user input';
|
||||||
|
dispatchEvent(input.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(form.value).toEqual({login: 'user input'});
|
||||||
|
|
||||||
|
form.setValue({login: 'Carson'});
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(input.nativeElement.value).toEqual('Carson');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach dirs to all child controls when group control changes', () => {
|
||||||
|
const fixture = TestBed.createComponent(NestedFormGroupComp);
|
||||||
|
const form = new FormGroup({
|
||||||
|
signin: new FormGroup(
|
||||||
|
{login: new FormControl('oldLogin'), password: new FormControl('oldPassword')})
|
||||||
|
});
|
||||||
|
fixture.debugElement.componentInstance.form = form;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
form.removeControl('signin');
|
||||||
|
form.addControl(
|
||||||
|
'signin',
|
||||||
|
new FormGroup(
|
||||||
|
{login: new FormControl('newLogin'), password: new FormControl('newPassword')}));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const inputs = fixture.debugElement.queryAll(By.css('input'));
|
||||||
|
expect(inputs[0].nativeElement.value).toEqual('newLogin');
|
||||||
|
expect(inputs[1].nativeElement.value).toEqual('newPassword');
|
||||||
|
|
||||||
|
inputs[0].nativeElement.value = 'user input';
|
||||||
|
dispatchEvent(inputs[0].nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(form.value).toEqual({signin: {login: 'user input', password: 'newPassword'}});
|
||||||
|
|
||||||
|
form.setValue({signin: {login: 'Carson', password: 'Drew'}});
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(inputs[0].nativeElement.value).toEqual('Carson');
|
||||||
|
expect(inputs[1].nativeElement.value).toEqual('Drew');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attach dirs to all present child controls when array control changes', () => {
|
||||||
|
const fixture = TestBed.createComponent(FormArrayComp);
|
||||||
|
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);
|
||||||
|
const form = new FormGroup({cities: cityArray});
|
||||||
|
fixture.debugElement.componentInstance.form = form;
|
||||||
|
fixture.debugElement.componentInstance.cityArray = cityArray;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
form.removeControl('cities');
|
||||||
|
form.addControl('cities', new FormArray([new FormControl('LA')]));
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input'));
|
||||||
|
expect(input.nativeElement.value).toEqual('LA');
|
||||||
|
|
||||||
|
input.nativeElement.value = 'MTV';
|
||||||
|
dispatchEvent(input.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(form.value).toEqual({cities: ['MTV']});
|
||||||
|
|
||||||
|
form.setValue({cities: ['LA']});
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(input.nativeElement.value).toEqual('LA');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -167,6 +167,7 @@ export declare class FormArray extends AbstractControl {
|
||||||
reset(value?: any, {onlySelf}?: {
|
reset(value?: any, {onlySelf}?: {
|
||||||
onlySelf?: boolean;
|
onlySelf?: boolean;
|
||||||
}): void;
|
}): void;
|
||||||
|
setControl(index: number, control: AbstractControl): void;
|
||||||
setValue(value: any[], {onlySelf}?: {
|
setValue(value: any[], {onlySelf}?: {
|
||||||
onlySelf?: boolean;
|
onlySelf?: boolean;
|
||||||
}): void;
|
}): void;
|
||||||
|
@ -272,6 +273,7 @@ export declare class FormGroup extends AbstractControl {
|
||||||
reset(value?: any, {onlySelf}?: {
|
reset(value?: any, {onlySelf}?: {
|
||||||
onlySelf?: boolean;
|
onlySelf?: boolean;
|
||||||
}): void;
|
}): void;
|
||||||
|
setControl(name: string, control: AbstractControl): void;
|
||||||
setValue(value: {
|
setValue(value: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, {onlySelf}?: {
|
}, {onlySelf}?: {
|
||||||
|
@ -282,27 +284,27 @@ export declare class FormGroup extends AbstractControl {
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class FormGroupDirective extends ControlContainer implements Form, OnChanges {
|
export declare class FormGroupDirective extends ControlContainer implements Form, OnChanges {
|
||||||
control: FormGroup;
|
control: FormGroup;
|
||||||
directives: NgControl[];
|
directives: FormControlName[];
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
formDirective: Form;
|
formDirective: Form;
|
||||||
ngSubmit: EventEmitter<{}>;
|
ngSubmit: EventEmitter<{}>;
|
||||||
path: string[];
|
path: string[];
|
||||||
submitted: boolean;
|
submitted: boolean;
|
||||||
constructor(_validators: any[], _asyncValidators: any[]);
|
constructor(_validators: any[], _asyncValidators: any[]);
|
||||||
addControl(dir: NgControl): void;
|
addControl(dir: FormControlName): FormControl;
|
||||||
addFormArray(dir: FormArrayName): void;
|
addFormArray(dir: FormArrayName): void;
|
||||||
addFormGroup(dir: FormGroupName): void;
|
addFormGroup(dir: FormGroupName): void;
|
||||||
getControl(dir: NgControl): FormControl;
|
getControl(dir: FormControlName): FormControl;
|
||||||
getFormArray(dir: FormArrayName): FormArray;
|
getFormArray(dir: FormArrayName): FormArray;
|
||||||
getFormGroup(dir: FormGroupName): FormGroup;
|
getFormGroup(dir: FormGroupName): FormGroup;
|
||||||
ngOnChanges(changes: SimpleChanges): void;
|
ngOnChanges(changes: SimpleChanges): void;
|
||||||
onReset(): void;
|
onReset(): void;
|
||||||
onSubmit(): boolean;
|
onSubmit(): boolean;
|
||||||
removeControl(dir: NgControl): void;
|
removeControl(dir: FormControlName): void;
|
||||||
removeFormArray(dir: FormArrayName): void;
|
removeFormArray(dir: FormArrayName): void;
|
||||||
removeFormGroup(dir: FormGroupName): void;
|
removeFormGroup(dir: FormGroupName): void;
|
||||||
resetForm(value?: any): void;
|
resetForm(value?: any): void;
|
||||||
updateModel(dir: NgControl, value: any): void;
|
updateModel(dir: FormControlName, value: any): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
|
|
Loading…
Reference in New Issue