feat(forms): add default updateOn values for groups and arrays (#18536)
This commit adds support for setting default `updateOn` values in `FormGroups` and `FormArrays`. If you set `updateOn` to ’blur’` at the group level, all child controls will default to `’blur’`, unless the child has explicitly specified a different `updateOn` value. ``` const c = new FormGroup({ one: new FormControl() }, {updateOn: blur}); ``` It's worth noting that parent groups will always update their value and validity immediately upon value/validity updates from children. In other words, if a group is set to update on blur and its children are individually set to update on change, the group will still update on change with its children; its default value will simply not be used.
This commit is contained in:
parent
dca50deae4
commit
ff5c58be6b
|
@ -150,7 +150,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
|
||||||
_syncPendingControls() {
|
_syncPendingControls() {
|
||||||
this.form._syncPendingControls();
|
this.form._syncPendingControls();
|
||||||
this.directives.forEach(dir => {
|
this.directives.forEach(dir => {
|
||||||
if (dir.control._updateOn === 'submit') {
|
if (dir.control.updateOn === 'submit') {
|
||||||
dir.viewToModelUpdate(dir.control._pendingValue);
|
dir.viewToModelUpdate(dir.control._pendingValue);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -84,7 +84,7 @@ function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
|
||||||
control._pendingValue = newValue;
|
control._pendingValue = newValue;
|
||||||
control._pendingDirty = true;
|
control._pendingDirty = true;
|
||||||
|
|
||||||
if (control._updateOn === 'change') updateControl(control, dir);
|
if (control.updateOn === 'change') updateControl(control, dir);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,8 +92,8 @@ function setUpBlurPipeline(control: FormControl, dir: NgControl): void {
|
||||||
dir.valueAccessor !.registerOnTouched(() => {
|
dir.valueAccessor !.registerOnTouched(() => {
|
||||||
control._pendingTouched = true;
|
control._pendingTouched = true;
|
||||||
|
|
||||||
if (control._updateOn === 'blur') updateControl(control, dir);
|
if (control.updateOn === 'blur') updateControl(control, dir);
|
||||||
if (control._updateOn !== 'submit') control.markAsTouched();
|
if (control.updateOn !== 'submit') control.markAsTouched();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -118,6 +118,9 @@ export abstract class AbstractControl {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_onCollectionChange = () => {};
|
_onCollectionChange = () => {};
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_updateOn: FormHooks;
|
||||||
|
|
||||||
private _valueChanges: EventEmitter<any>;
|
private _valueChanges: EventEmitter<any>;
|
||||||
private _statusChanges: EventEmitter<any>;
|
private _statusChanges: EventEmitter<any>;
|
||||||
private _status: string;
|
private _status: string;
|
||||||
|
@ -242,6 +245,15 @@ export abstract class AbstractControl {
|
||||||
*/
|
*/
|
||||||
get statusChanges(): Observable<any> { return this._statusChanges; }
|
get statusChanges(): Observable<any> { return this._statusChanges; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the update strategy of the `AbstractControl` (i.e.
|
||||||
|
* the event on which the control will update itself).
|
||||||
|
* Possible values: `'change'` (default) | `'blur'` | `'submit'`
|
||||||
|
*/
|
||||||
|
get updateOn(): FormHooks {
|
||||||
|
return this._updateOn ? this._updateOn : (this.parent ? this.parent.updateOn : 'change');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the synchronous validators that are active on this control. Calling
|
* Sets the synchronous validators that are active on this control. Calling
|
||||||
* this will overwrite any existing sync validators.
|
* this will overwrite any existing sync validators.
|
||||||
|
@ -624,6 +636,13 @@ export abstract class AbstractControl {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_registerOnCollectionChange(fn: () => void): void { this._onCollectionChange = fn; }
|
_registerOnCollectionChange(fn: () => void): void { this._onCollectionChange = fn; }
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
_setUpdateStrategy(opts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): void {
|
||||||
|
if (isOptionsObj(opts) && (opts as AbstractControlOptions).updateOn != null) {
|
||||||
|
this._updateOn = (opts as AbstractControlOptions).updateOn !;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -697,9 +716,6 @@ export class FormControl extends AbstractControl {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_onChange: Function[] = [];
|
_onChange: Function[] = [];
|
||||||
|
|
||||||
/** @internal */
|
|
||||||
_updateOn: FormHooks = 'change';
|
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_pendingValue: any;
|
_pendingValue: any;
|
||||||
|
|
||||||
|
@ -841,7 +857,7 @@ export class FormControl extends AbstractControl {
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
_syncPendingControls(): boolean {
|
_syncPendingControls(): boolean {
|
||||||
if (this._updateOn === 'submit') {
|
if (this.updateOn === 'submit') {
|
||||||
this.setValue(this._pendingValue, {onlySelf: true, emitModelToViewChange: false});
|
this.setValue(this._pendingValue, {onlySelf: true, emitModelToViewChange: false});
|
||||||
if (this._pendingDirty) this.markAsDirty();
|
if (this._pendingDirty) this.markAsDirty();
|
||||||
if (this._pendingTouched) this.markAsTouched();
|
if (this._pendingTouched) this.markAsTouched();
|
||||||
|
@ -859,12 +875,6 @@ export class FormControl extends AbstractControl {
|
||||||
this._value = this._pendingValue = formState;
|
this._value = this._pendingValue = formState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setUpdateStrategy(opts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): void {
|
|
||||||
if (isOptionsObj(opts) && (opts as AbstractControlOptions).updateOn != null) {
|
|
||||||
this._updateOn = (opts as AbstractControlOptions).updateOn !;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -925,6 +935,17 @@ export class FormControl extends AbstractControl {
|
||||||
* }, {validators: passwordMatchValidator, asyncValidators: otherValidator});
|
* }, {validators: passwordMatchValidator, asyncValidators: otherValidator});
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* The options object can also be used to set a default value for each child
|
||||||
|
* control's `updateOn` property. If you set `updateOn` to `'blur'` at the
|
||||||
|
* group level, all child controls will default to 'blur', unless the child
|
||||||
|
* has explicitly specified a different `updateOn` value.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const c = new FormGroup({
|
||||||
|
* one: new FormControl()
|
||||||
|
* }, {updateOn: 'blur'});
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* * **npm package**: `@angular/forms`
|
* * **npm package**: `@angular/forms`
|
||||||
*
|
*
|
||||||
* @stable
|
* @stable
|
||||||
|
@ -938,6 +959,7 @@ export class FormGroup extends AbstractControl {
|
||||||
coerceToValidator(validatorOrOpts),
|
coerceToValidator(validatorOrOpts),
|
||||||
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
|
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
|
||||||
this._initObservables();
|
this._initObservables();
|
||||||
|
this._setUpdateStrategy(validatorOrOpts);
|
||||||
this._setUpControls();
|
this._setUpControls();
|
||||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||||
}
|
}
|
||||||
|
@ -1242,6 +1264,17 @@ export class FormGroup extends AbstractControl {
|
||||||
* ], {validators: myValidator, asyncValidators: myAsyncValidator});
|
* ], {validators: myValidator, asyncValidators: myAsyncValidator});
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
|
* The options object can also be used to set a default value for each child
|
||||||
|
* control's `updateOn` property. If you set `updateOn` to `'blur'` at the
|
||||||
|
* array level, all child controls will default to 'blur', unless the child
|
||||||
|
* has explicitly specified a different `updateOn` value.
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* const c = new FormArray([
|
||||||
|
* new FormControl()
|
||||||
|
* ], {updateOn: 'blur'});
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* ### Adding or removing controls
|
* ### Adding or removing controls
|
||||||
*
|
*
|
||||||
* To change the controls in the array, use the `push`, `insert`, or `removeAt` methods
|
* To change the controls in the array, use the `push`, `insert`, or `removeAt` methods
|
||||||
|
@ -1263,6 +1296,7 @@ export class FormArray extends AbstractControl {
|
||||||
coerceToValidator(validatorOrOpts),
|
coerceToValidator(validatorOrOpts),
|
||||||
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
|
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
|
||||||
this._initObservables();
|
this._initObservables();
|
||||||
|
this._setUpdateStrategy(validatorOrOpts);
|
||||||
this._setUpControls();
|
this._setUpControls();
|
||||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,17 +80,84 @@ export function main() {
|
||||||
|
|
||||||
it('should default to on change', () => {
|
it('should default to on change', () => {
|
||||||
const c = new FormControl('');
|
const c = new FormControl('');
|
||||||
expect(c._updateOn).toEqual('change');
|
expect(c.updateOn).toEqual('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should default to on change with an options obj', () => {
|
it('should default to on change with an options obj', () => {
|
||||||
const c = new FormControl('', {validators: Validators.required});
|
const c = new FormControl('', {validators: Validators.required});
|
||||||
expect(c._updateOn).toEqual('change');
|
expect(c.updateOn).toEqual('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set updateOn when updating on blur', () => {
|
it('should set updateOn when updating on blur', () => {
|
||||||
const c = new FormControl('', {updateOn: 'blur'});
|
const c = new FormControl('', {updateOn: 'blur'});
|
||||||
expect(c._updateOn).toEqual('blur');
|
expect(c.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('in groups and arrays', () => {
|
||||||
|
it('should default to group updateOn when not set in control', () => {
|
||||||
|
const g =
|
||||||
|
new FormGroup({one: new FormControl(), two: new FormControl()}, {updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(g.get('one') !.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('two') !.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should default to array updateOn when not set in control', () => {
|
||||||
|
const a = new FormArray([new FormControl(), new FormControl()], {updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(a.get([0]) !.updateOn).toEqual('blur');
|
||||||
|
expect(a.get([1]) !.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set updateOn with nested groups', () => {
|
||||||
|
const g = new FormGroup(
|
||||||
|
{
|
||||||
|
group: new FormGroup({one: new FormControl(), two: new FormControl()}),
|
||||||
|
},
|
||||||
|
{updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(g.get('group.one') !.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('group.two') !.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('group') !.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set updateOn with nested arrays', () => {
|
||||||
|
const g = new FormGroup(
|
||||||
|
{
|
||||||
|
arr: new FormArray([new FormControl(), new FormControl()]),
|
||||||
|
},
|
||||||
|
{updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(g.get(['arr', 0]) !.updateOn).toEqual('blur');
|
||||||
|
expect(g.get(['arr', 1]) !.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('arr') !.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow control updateOn to override group updateOn', () => {
|
||||||
|
const g = new FormGroup(
|
||||||
|
{one: new FormControl('', {updateOn: 'change'}), two: new FormControl()},
|
||||||
|
{updateOn: 'blur'});
|
||||||
|
|
||||||
|
expect(g.get('one') !.updateOn).toEqual('change');
|
||||||
|
expect(g.get('two') !.updateOn).toEqual('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set updateOn with complex setup', () => {
|
||||||
|
const g = new FormGroup({
|
||||||
|
group: new FormGroup(
|
||||||
|
{one: new FormControl('', {updateOn: 'change'}), two: new FormControl()},
|
||||||
|
{updateOn: 'blur'}),
|
||||||
|
groupTwo: new FormGroup({one: new FormControl()}, {updateOn: 'submit'}),
|
||||||
|
three: new FormControl()
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(g.get('group.one') !.updateOn).toEqual('change');
|
||||||
|
expect(g.get('group.two') !.updateOn).toEqual('blur');
|
||||||
|
expect(g.get('groupTwo.one') !.updateOn).toEqual('submit');
|
||||||
|
expect(g.get('three') !.updateOn).toEqual('change');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -931,7 +931,6 @@ export function main() {
|
||||||
sub.unsubscribe();
|
sub.unsubscribe();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should mark as pristine properly if pending dirty', () => {
|
it('should mark as pristine properly if pending dirty', () => {
|
||||||
const fixture = initTest(FormControlComp);
|
const fixture = initTest(FormControlComp);
|
||||||
const control = new FormControl('', {updateOn: 'blur'});
|
const control = new FormControl('', {updateOn: 'blur'});
|
||||||
|
@ -955,6 +954,94 @@ export function main() {
|
||||||
expect(control.dirty).toBe(false, 'Expected pending dirty value to reset.');
|
expect(control.dirty).toBe(false, 'Expected pending dirty value to reset.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update on blur with group updateOn', () => {
|
||||||
|
const fixture = initTest(FormGroupComp);
|
||||||
|
const control = new FormControl('', Validators.required);
|
||||||
|
const formGroup = new FormGroup({login: control}, {updateOn: 'blur'});
|
||||||
|
fixture.componentInstance.form = formGroup;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||||
|
input.value = 'Nancy';
|
||||||
|
dispatchEvent(input, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('', 'Expected value to remain unchanged until blur.');
|
||||||
|
expect(control.valid).toBe(false, 'Expected no validation to occur until blur.');
|
||||||
|
|
||||||
|
dispatchEvent(input, 'blur');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value)
|
||||||
|
.toEqual('Nancy', 'Expected value to change once control is blurred.');
|
||||||
|
expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update on blur with array updateOn', () => {
|
||||||
|
const fixture = initTest(FormArrayComp);
|
||||||
|
const control = new FormControl('', Validators.required);
|
||||||
|
const cityArray = new FormArray([control], {updateOn: 'blur'});
|
||||||
|
const formGroup = new FormGroup({cities: cityArray});
|
||||||
|
fixture.componentInstance.form = formGroup;
|
||||||
|
fixture.componentInstance.cityArray = cityArray;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||||
|
input.value = 'Nancy';
|
||||||
|
dispatchEvent(input, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('', 'Expected value to remain unchanged until blur.');
|
||||||
|
expect(control.valid).toBe(false, 'Expected no validation to occur until blur.');
|
||||||
|
|
||||||
|
dispatchEvent(input, 'blur');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value)
|
||||||
|
.toEqual('Nancy', 'Expected value to change once control is blurred.');
|
||||||
|
expect(control.valid).toBe(true, 'Expected validation to run once control is blurred.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should allow child control updateOn blur to override group updateOn', () => {
|
||||||
|
const fixture = initTest(NestedFormGroupComp);
|
||||||
|
const loginControl =
|
||||||
|
new FormControl('', {validators: Validators.required, updateOn: 'change'});
|
||||||
|
const passwordControl = new FormControl('', Validators.required);
|
||||||
|
const formGroup = new FormGroup(
|
||||||
|
{signin: new FormGroup({login: loginControl, password: passwordControl})},
|
||||||
|
{updateOn: 'blur'});
|
||||||
|
fixture.componentInstance.form = formGroup;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const [loginInput, passwordInput] = fixture.debugElement.queryAll(By.css('input'));
|
||||||
|
loginInput.nativeElement.value = 'Nancy';
|
||||||
|
dispatchEvent(loginInput.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(loginControl.value).toEqual('Nancy', 'Expected value change on input.');
|
||||||
|
expect(loginControl.valid).toBe(true, 'Expected validation to run on input.');
|
||||||
|
|
||||||
|
passwordInput.nativeElement.value = 'Carson';
|
||||||
|
dispatchEvent(passwordInput.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(passwordControl.value)
|
||||||
|
.toEqual('', 'Expected value to remain unchanged until blur.');
|
||||||
|
expect(passwordControl.valid).toBe(false, 'Expected no validation to occur until blur.');
|
||||||
|
|
||||||
|
dispatchEvent(passwordInput.nativeElement, 'blur');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(passwordControl.value)
|
||||||
|
.toEqual('Carson', 'Expected value to change once control is blurred.');
|
||||||
|
expect(passwordControl.valid)
|
||||||
|
.toBe(true, 'Expected validation to run once control is blurred.');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('on submit', () => {
|
describe('on submit', () => {
|
||||||
|
@ -1193,7 +1280,6 @@ export function main() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('should mark as untouched properly if pending touched', () => {
|
it('should mark as untouched properly if pending touched', () => {
|
||||||
const fixture = initTest(FormGroupComp);
|
const fixture = initTest(FormGroupComp);
|
||||||
const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
|
const formGroup = new FormGroup({login: new FormControl('', {updateOn: 'submit'})});
|
||||||
|
@ -1216,6 +1302,99 @@ export function main() {
|
||||||
expect(formGroup.touched).toBe(false, 'Expected touched to stay false on submit.');
|
expect(formGroup.touched).toBe(false, 'Expected touched to stay false on submit.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update on submit with group updateOn', () => {
|
||||||
|
const fixture = initTest(FormGroupComp);
|
||||||
|
const control = new FormControl('', Validators.required);
|
||||||
|
const formGroup = new FormGroup({login: control}, {updateOn: 'submit'});
|
||||||
|
fixture.componentInstance.form = formGroup;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||||
|
input.value = 'Nancy';
|
||||||
|
dispatchEvent(input, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('', 'Expected value to remain unchanged until submit.');
|
||||||
|
expect(control.valid).toBe(false, 'Expected no validation to occur until submit.');
|
||||||
|
|
||||||
|
dispatchEvent(input, 'blur');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('', 'Expected value to remain unchanged until submit.');
|
||||||
|
expect(control.valid).toBe(false, 'Expected no validation to occur until submit.');
|
||||||
|
|
||||||
|
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||||
|
dispatchEvent(form, 'submit');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('Nancy', 'Expected value to change on submit.');
|
||||||
|
expect(control.valid).toBe(true, 'Expected validation to run on submit.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update on submit with array updateOn', () => {
|
||||||
|
const fixture = initTest(FormArrayComp);
|
||||||
|
const control = new FormControl('', Validators.required);
|
||||||
|
const cityArray = new FormArray([control], {updateOn: 'submit'});
|
||||||
|
const formGroup = new FormGroup({cities: cityArray});
|
||||||
|
fixture.componentInstance.form = formGroup;
|
||||||
|
fixture.componentInstance.cityArray = cityArray;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||||
|
input.value = 'Nancy';
|
||||||
|
dispatchEvent(input, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('', 'Expected value to remain unchanged until submit.');
|
||||||
|
expect(control.valid).toBe(false, 'Expected no validation to occur until submit.');
|
||||||
|
|
||||||
|
|
||||||
|
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||||
|
dispatchEvent(form, 'submit');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(control.value).toEqual('Nancy', 'Expected value to change once control on submit');
|
||||||
|
expect(control.valid).toBe(true, 'Expected validation to run on submit.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow child control updateOn submit to override group updateOn', () => {
|
||||||
|
const fixture = initTest(NestedFormGroupComp);
|
||||||
|
const loginControl =
|
||||||
|
new FormControl('', {validators: Validators.required, updateOn: 'change'});
|
||||||
|
const passwordControl = new FormControl('', Validators.required);
|
||||||
|
const formGroup = new FormGroup(
|
||||||
|
{signin: new FormGroup({login: loginControl, password: passwordControl})},
|
||||||
|
{updateOn: 'submit'});
|
||||||
|
fixture.componentInstance.form = formGroup;
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const [loginInput, passwordInput] = fixture.debugElement.queryAll(By.css('input'));
|
||||||
|
loginInput.nativeElement.value = 'Nancy';
|
||||||
|
dispatchEvent(loginInput.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(loginControl.value).toEqual('Nancy', 'Expected value change on input.');
|
||||||
|
expect(loginControl.valid).toBe(true, 'Expected validation to run on input.');
|
||||||
|
|
||||||
|
passwordInput.nativeElement.value = 'Carson';
|
||||||
|
dispatchEvent(passwordInput.nativeElement, 'input');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(passwordControl.value)
|
||||||
|
.toEqual('', 'Expected value to remain unchanged until submit.');
|
||||||
|
expect(passwordControl.valid)
|
||||||
|
.toBe(false, 'Expected no validation to occur until submit.');
|
||||||
|
|
||||||
|
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||||
|
dispatchEvent(form, 'submit');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(passwordControl.value).toEqual('Carson', 'Expected value to change on submit.');
|
||||||
|
expect(passwordControl.valid).toBe(true, 'Expected validation to run on submit.');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -2008,13 +2187,13 @@ class NestedFormGroupComp {
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'form-array-comp',
|
selector: 'form-array-comp',
|
||||||
template: `
|
template: `
|
||||||
<div [formGroup]="form">
|
<form [formGroup]="form">
|
||||||
<div formArrayName="cities">
|
<div formArrayName="cities">
|
||||||
<div *ngFor="let city of cityArray.controls; let i=index">
|
<div *ngFor="let city of cityArray.controls; let i=index">
|
||||||
<input [formControlName]="i">
|
<input [formControlName]="i">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`
|
</form>`
|
||||||
})
|
})
|
||||||
class FormArrayComp {
|
class FormArrayComp {
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
|
|
|
@ -14,6 +14,7 @@ export declare abstract class AbstractControl {
|
||||||
readonly statusChanges: Observable<any>;
|
readonly statusChanges: Observable<any>;
|
||||||
readonly touched: boolean;
|
readonly touched: boolean;
|
||||||
readonly untouched: boolean;
|
readonly untouched: boolean;
|
||||||
|
readonly updateOn: FormHooks;
|
||||||
readonly valid: boolean;
|
readonly valid: boolean;
|
||||||
validator: ValidatorFn | null;
|
validator: ValidatorFn | null;
|
||||||
readonly value: any;
|
readonly value: any;
|
||||||
|
|
Loading…
Reference in New Issue