Skip to content

Commit

Permalink
feat(forms): add updateOn submit option to FormControls (#18514)
Browse files Browse the repository at this point in the history
  • Loading branch information
kara authored and vicb committed Aug 7, 2017
1 parent 685cc26 commit f69561b
Show file tree
Hide file tree
Showing 4 changed files with 411 additions and 20 deletions.
Expand Up @@ -134,6 +134,7 @@ export class FormGroupDirective extends ControlContainer implements Form,

onSubmit($event: Event): boolean {
this._submitted = true;
this._syncPendingControls();
this.ngSubmit.emit($event);
return false;
}
Expand All @@ -145,6 +146,16 @@ export class FormGroupDirective extends ControlContainer implements Form,
this._submitted = false;
}

/** @internal */
_syncPendingControls() {
this.form._syncPendingControls();
this.directives.forEach(dir => {
if (dir.control._updateOn === 'submit') {
dir.viewToModelUpdate(dir.control._pendingValue);
}
});
}

/** @internal */
_updateDomValue() {
this.directives.forEach(dir => {
Expand Down
22 changes: 11 additions & 11 deletions packages/forms/src/directives/shared.ts
Expand Up @@ -84,25 +84,25 @@ function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
control._pendingValue = newValue;
control._pendingDirty = true;

if (control._updateOn === 'change') {
dir.viewToModelUpdate(newValue);
control.markAsDirty();
control.setValue(newValue, {emitModelToViewChange: false});
}
if (control._updateOn === 'change') updateControl(control, dir);
});
}

function setUpBlurPipeline(control: FormControl, dir: NgControl): void {
dir.valueAccessor !.registerOnTouched(() => {
if (control._updateOn === 'blur') {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}
control.markAsTouched();
control._pendingTouched = true;

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

function updateControl(control: FormControl, dir: NgControl): void {
dir.viewToModelUpdate(control._pendingValue);
if (control._pendingDirty) control.markAsDirty();
control.setValue(control._pendingValue, {emitModelToViewChange: false});
}

function setUpModelChangePipeline(control: FormControl, dir: NgControl): void {
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
// control -> view
Expand Down
50 changes: 45 additions & 5 deletions packages/forms/src/model.ts
Expand Up @@ -78,7 +78,7 @@ function coerceToAsyncValidator(
origAsyncValidator || null;
}

export type FormHooks = 'change' | 'blur';
export type FormHooks = 'change' | 'blur' | 'submit';

export interface AbstractControlOptions {
validators?: ValidatorFn|ValidatorFn[]|null;
Expand Down Expand Up @@ -108,6 +108,13 @@ function isOptionsObj(
export abstract class AbstractControl {
/** @internal */
_value: any;

/** @internal */
_pendingDirty: boolean;

/** @internal */
_pendingTouched: boolean;

/** @internal */
_onCollectionChange = () => {};

Expand Down Expand Up @@ -284,6 +291,7 @@ export abstract class AbstractControl {
*/
markAsUntouched(opts: {onlySelf?: boolean} = {}): void {
this._touched = false;
this._pendingTouched = false;

this._forEachChild(
(control: AbstractControl) => { control.markAsUntouched({onlySelf: true}); });
Expand Down Expand Up @@ -316,6 +324,7 @@ export abstract class AbstractControl {
*/
markAsPristine(opts: {onlySelf?: boolean} = {}): void {
this._pristine = true;
this._pendingDirty = false;

this._forEachChild((control: AbstractControl) => { control.markAsPristine({onlySelf: true}); });

Expand Down Expand Up @@ -568,6 +577,9 @@ export abstract class AbstractControl {
/** @internal */
abstract _allControlsDisabled(): boolean;

/** @internal */
abstract _syncPendingControls(): boolean;

/** @internal */
_anyControlsHaveStatus(status: string): boolean {
return this._anyControls((control: AbstractControl) => control.status === status);
Expand Down Expand Up @@ -672,6 +684,9 @@ export abstract class AbstractControl {
* const c = new FormControl('', { updateOn: 'blur' });
* ```
*
* You can also set `updateOn` to `'submit'`, which will delay value and validity
* updates until the parent form of the control fires a submit event.
*
* See its superclass, {@link AbstractControl}, for more properties and methods.
*
* * **npm package**: `@angular/forms`
Expand All @@ -688,9 +703,6 @@ export class FormControl extends AbstractControl {
/** @internal */
_pendingValue: any;

/** @internal */
_pendingDirty: boolean;

constructor(
formState: any = null,
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null,
Expand Down Expand Up @@ -782,7 +794,6 @@ export class FormControl extends AbstractControl {
reset(formState: any = null, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
this._applyFormState(formState);
this.markAsPristine(options);
this._pendingDirty = false;
this.markAsUntouched(options);
this.setValue(this._value, options);
}
Expand Down Expand Up @@ -828,6 +839,17 @@ export class FormControl extends AbstractControl {
*/
_forEachChild(cb: Function): void {}

/** @internal */
_syncPendingControls(): boolean {
if (this._updateOn === 'submit') {
this.setValue(this._pendingValue, {onlySelf: true, emitModelToViewChange: false});
if (this._pendingDirty) this.markAsDirty();
if (this._pendingTouched) this.markAsTouched();
return true;
}
return false;
}

private _applyFormState(formState: any) {
if (this._isBoxedValue(formState)) {
this._value = this._pendingValue = formState.value;
Expand Down Expand Up @@ -1092,6 +1114,15 @@ export class FormGroup extends AbstractControl {
});
}

/** @internal */
_syncPendingControls(): boolean {
let subtreeUpdated = this._reduceChildren(false, (updated: boolean, child: AbstractControl) => {
return child._syncPendingControls() ? true : updated;
});
if (subtreeUpdated) this.updateValueAndValidity({onlySelf: true});
return subtreeUpdated;
}

/** @internal */
_throwIfControlMissing(name: string): void {
if (!Object.keys(this.controls).length) {
Expand Down Expand Up @@ -1404,6 +1435,15 @@ export class FormArray extends AbstractControl {
});
}

/** @internal */
_syncPendingControls(): boolean {
let subtreeUpdated = this.controls.reduce((updated: boolean, child: AbstractControl) => {
return child._syncPendingControls() ? true : updated;
}, false);
if (subtreeUpdated) this.updateValueAndValidity({onlySelf: true});
return subtreeUpdated;
}

/** @internal */
_throwIfControlMissing(index: number): void {
if (!this.controls.length) {
Expand Down

0 comments on commit f69561b

Please sign in to comment.