Skip to content
Permalink
Browse files

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.
  • Loading branch information...
kara authored and vicb committed Aug 9, 2017
1 parent dca50de commit ff5c58be6b843bc8f79613dbefc5e4951c125f3d
@@ -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);
}
});
@@ -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();
});
}

@@ -118,6 +118,9 @@ export abstract class AbstractControl {
/** @internal */
_onCollectionChange = () => {};

/** @internal */
_updateOn: FormHooks;

private _valueChanges: EventEmitter<any>;
private _statusChanges: EventEmitter<any>;
private _status: string;
@@ -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.
@@ -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 !;
}
}
}

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

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

/** @internal */
_pendingValue: any;

@@ -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();
@@ -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 !;
}
}
}

/**
@@ -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
@@ -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});
}
@@ -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
@@ -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});
}
@@ -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');
});


});

});
Oops, something went wrong.

0 comments on commit ff5c58b

Please sign in to comment.
You can’t perform that action at this time.