Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class FormGroupDirective extends ControlContainer implements Form,
_syncPendingControls() {
this.form._syncPendingControls();
this.directives.forEach(dir => {
if (dir.control._updateOn === 'submit') {
if (dir.control.updateOn === 'submit') {
dir.viewToModelUpdate(dir.control._pendingValue);
}
});
Expand Down
6 changes: 3 additions & 3 deletions packages/forms/src/directives/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,16 @@ function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
control._pendingValue = newValue;
control._pendingDirty = true;

if (control._updateOn === 'change') updateControl(control, dir);
if (control.updateOn === 'change') updateControl(control, dir);
});
}

function setUpBlurPipeline(control: FormControl, dir: NgControl): void {
dir.valueAccessor !.registerOnTouched(() => {
control._pendingTouched = true;

if (control._updateOn === 'blur') updateControl(control, dir);
if (control._updateOn !== 'submit') control.markAsTouched();
if (control.updateOn === 'blur') updateControl(control, dir);
if (control.updateOn !== 'submit') control.markAsTouched();
});
}

Expand Down
54 changes: 44 additions & 10 deletions packages/forms/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ export abstract class AbstractControl {
/** @internal */
_onCollectionChange = () => {};

/** @internal */
_updateOn: FormHooks;

private _valueChanges: EventEmitter<any>;
private _statusChanges: EventEmitter<any>;
private _status: string;
Expand Down Expand Up @@ -242,6 +245,15 @@ export abstract class AbstractControl {
*/
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
* this will overwrite any existing sync validators.
Expand Down Expand Up @@ -624,6 +636,13 @@ export abstract class AbstractControl {

/** @internal */
_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 !;
}
}
}

/**
Expand Down Expand Up @@ -697,9 +716,6 @@ export class FormControl extends AbstractControl {
/** @internal */
_onChange: Function[] = [];

/** @internal */
_updateOn: FormHooks = 'change';

/** @internal */
_pendingValue: any;

Expand Down Expand Up @@ -841,7 +857,7 @@ export class FormControl extends AbstractControl {

/** @internal */
_syncPendingControls(): boolean {
if (this._updateOn === 'submit') {
if (this.updateOn === 'submit') {
this.setValue(this._pendingValue, {onlySelf: true, emitModelToViewChange: false});
if (this._pendingDirty) this.markAsDirty();
if (this._pendingTouched) this.markAsTouched();
Expand All @@ -859,12 +875,6 @@ export class FormControl extends AbstractControl {
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 !;
}
}
}

/**
Expand Down Expand Up @@ -925,6 +935,17 @@ export class FormControl extends AbstractControl {
* }, {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`
*
* @stable
Expand All @@ -938,6 +959,7 @@ export class FormGroup extends AbstractControl {
coerceToValidator(validatorOrOpts),
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
this._initObservables();
this._setUpdateStrategy(validatorOrOpts);
this._setUpControls();
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
}
Expand Down Expand Up @@ -1242,6 +1264,17 @@ export class FormGroup extends AbstractControl {
* ], {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
*
* To change the controls in the array, use the `push`, `insert`, or `removeAt` methods
Expand All @@ -1263,6 +1296,7 @@ export class FormArray extends AbstractControl {
coerceToValidator(validatorOrOpts),
coerceToAsyncValidator(asyncValidator, validatorOrOpts));
this._initObservables();
this._setUpdateStrategy(validatorOrOpts);
this._setUpControls();
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
}
Expand Down
73 changes: 70 additions & 3 deletions packages/forms/test/form_control_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,17 +80,84 @@ export function main() {

it('should default to on change', () => {
const c = new FormControl('');
expect(c._updateOn).toEqual('change');
expect(c.updateOn).toEqual('change');
});

it('should default to on change with an options obj', () => {
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', () => {
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');
});


});

});
Expand Down
Loading