@@ -78,7 +78,7 @@ function coerceToAsyncValidator(
78
78
origAsyncValidator || null ;
79
79
}
80
80
81
- export type FormHooks = 'change' | 'blur' ;
81
+ export type FormHooks = 'change' | 'blur' | 'submit' ;
82
82
83
83
export interface AbstractControlOptions {
84
84
validators ?: ValidatorFn | ValidatorFn [ ] | null ;
@@ -108,6 +108,13 @@ function isOptionsObj(
108
108
export abstract class AbstractControl {
109
109
/** @internal */
110
110
_value : any ;
111
+
112
+ /** @internal */
113
+ _pendingDirty : boolean ;
114
+
115
+ /** @internal */
116
+ _pendingTouched : boolean ;
117
+
111
118
/** @internal */
112
119
_onCollectionChange = ( ) => { } ;
113
120
@@ -284,6 +291,7 @@ export abstract class AbstractControl {
284
291
*/
285
292
markAsUntouched ( opts : { onlySelf ?: boolean } = { } ) : void {
286
293
this . _touched = false ;
294
+ this . _pendingTouched = false ;
287
295
288
296
this . _forEachChild (
289
297
( control : AbstractControl ) => { control . markAsUntouched ( { onlySelf : true } ) ; } ) ;
@@ -316,6 +324,7 @@ export abstract class AbstractControl {
316
324
*/
317
325
markAsPristine ( opts : { onlySelf ?: boolean } = { } ) : void {
318
326
this . _pristine = true ;
327
+ this . _pendingDirty = false ;
319
328
320
329
this . _forEachChild ( ( control : AbstractControl ) => { control . markAsPristine ( { onlySelf : true } ) ; } ) ;
321
330
@@ -568,6 +577,9 @@ export abstract class AbstractControl {
568
577
/** @internal */
569
578
abstract _allControlsDisabled ( ) : boolean ;
570
579
580
+ /** @internal */
581
+ abstract _syncPendingControls ( ) : boolean ;
582
+
571
583
/** @internal */
572
584
_anyControlsHaveStatus ( status : string ) : boolean {
573
585
return this . _anyControls ( ( control : AbstractControl ) => control . status === status ) ;
@@ -672,6 +684,9 @@ export abstract class AbstractControl {
672
684
* const c = new FormControl('', { updateOn: 'blur' });
673
685
* ```
674
686
*
687
+ * You can also set `updateOn` to `'submit'`, which will delay value and validity
688
+ * updates until the parent form of the control fires a submit event.
689
+ *
675
690
* See its superclass, {@link AbstractControl}, for more properties and methods.
676
691
*
677
692
* * **npm package**: `@angular/forms`
@@ -688,9 +703,6 @@ export class FormControl extends AbstractControl {
688
703
/** @internal */
689
704
_pendingValue : any ;
690
705
691
- /** @internal */
692
- _pendingDirty : boolean ;
693
-
694
706
constructor (
695
707
formState : any = null ,
696
708
validatorOrOpts ?: ValidatorFn | ValidatorFn [ ] | AbstractControlOptions | null ,
@@ -782,7 +794,6 @@ export class FormControl extends AbstractControl {
782
794
reset ( formState : any = null , options : { onlySelf ?: boolean , emitEvent ?: boolean } = { } ) : void {
783
795
this . _applyFormState ( formState ) ;
784
796
this . markAsPristine ( options ) ;
785
- this . _pendingDirty = false ;
786
797
this . markAsUntouched ( options ) ;
787
798
this . setValue ( this . _value , options ) ;
788
799
}
@@ -828,6 +839,17 @@ export class FormControl extends AbstractControl {
828
839
*/
829
840
_forEachChild ( cb : Function ) : void { }
830
841
842
+ /** @internal */
843
+ _syncPendingControls ( ) : boolean {
844
+ if ( this . _updateOn === 'submit' ) {
845
+ this . setValue ( this . _pendingValue , { onlySelf : true , emitModelToViewChange : false } ) ;
846
+ if ( this . _pendingDirty ) this . markAsDirty ( ) ;
847
+ if ( this . _pendingTouched ) this . markAsTouched ( ) ;
848
+ return true ;
849
+ }
850
+ return false ;
851
+ }
852
+
831
853
private _applyFormState ( formState : any ) {
832
854
if ( this . _isBoxedValue ( formState ) ) {
833
855
this . _value = this . _pendingValue = formState . value ;
@@ -1092,6 +1114,15 @@ export class FormGroup extends AbstractControl {
1092
1114
} ) ;
1093
1115
}
1094
1116
1117
+ /** @internal */
1118
+ _syncPendingControls ( ) : boolean {
1119
+ let subtreeUpdated = this . _reduceChildren ( false , ( updated : boolean , child : AbstractControl ) => {
1120
+ return child . _syncPendingControls ( ) ? true : updated ;
1121
+ } ) ;
1122
+ if ( subtreeUpdated ) this . updateValueAndValidity ( { onlySelf : true } ) ;
1123
+ return subtreeUpdated ;
1124
+ }
1125
+
1095
1126
/** @internal */
1096
1127
_throwIfControlMissing ( name : string ) : void {
1097
1128
if ( ! Object . keys ( this . controls ) . length ) {
@@ -1404,6 +1435,15 @@ export class FormArray extends AbstractControl {
1404
1435
} ) ;
1405
1436
}
1406
1437
1438
+ /** @internal */
1439
+ _syncPendingControls ( ) : boolean {
1440
+ let subtreeUpdated = this . controls . reduce ( ( updated : boolean , child : AbstractControl ) => {
1441
+ return child . _syncPendingControls ( ) ? true : updated ;
1442
+ } , false ) ;
1443
+ if ( subtreeUpdated ) this . updateValueAndValidity ( { onlySelf : true } ) ;
1444
+ return subtreeUpdated ;
1445
+ }
1446
+
1407
1447
/** @internal */
1408
1448
_throwIfControlMissing ( index : number ) : void {
1409
1449
if ( ! this . controls . length ) {
0 commit comments