diff --git a/aio/content/guide/form-validation.md b/aio/content/guide/form-validation.md
index 3badceaa971c..268b62fa7957 100644
--- a/aio/content/guide/form-validation.md
+++ b/aio/content/guide/form-validation.md
@@ -191,6 +191,7 @@ Like in AngularJS, Angular automatically mirrors many control properties onto th
* `.ng-dirty`
* `.ng-untouched`
* `.ng-touched`
+* `.ng-submitted`
The hero form uses the `.ng-valid` and `.ng-invalid` classes to
set the color of each form control's border.
diff --git a/aio/content/guide/forms.md b/aio/content/guide/forms.md
index d7a5e82739f0..3217c6c4bf72 100644
--- a/aio/content/guide/forms.md
+++ b/aio/content/guide/forms.md
@@ -476,6 +476,22 @@ You can leverage those class names to change the appearance of the control.
+
+
+ |
+ The parent form (if any) has been submitted.
+ |
+
+
+ ng-submitted
+ |
+
+
+ None
+ |
+
+
+
Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy`
diff --git a/packages/forms/src/directives/ng_control_status.ts b/packages/forms/src/directives/ng_control_status.ts
index 735c1b593846..530faa93e74d 100644
--- a/packages/forms/src/directives/ng_control_status.ts
+++ b/packages/forms/src/directives/ng_control_status.ts
@@ -6,16 +6,22 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {Directive, Self} from '@angular/core';
+import {Directive, Optional, Self} from '@angular/core';
import {AbstractControlDirective} from './abstract_control_directive';
import {ControlContainer} from './control_container';
import {NgControl} from './ng_control';
+import {NgForm} from './ng_form';
+import {FormGroupDirective} from './reactive_directives/form_group_directive';
export class AbstractControlStatus {
private _cd: AbstractControlDirective;
+ private _controlContainer: ControlContainer|undefined;
- constructor(cd: AbstractControlDirective) { this._cd = cd; }
+ constructor(cd: AbstractControlDirective, controlContainer?: ControlContainer) {
+ this._cd = cd;
+ this._controlContainer = controlContainer;
+ }
get ngClassUntouched(): boolean { return this._cd.control ? this._cd.control.untouched : false; }
get ngClassTouched(): boolean { return this._cd.control ? this._cd.control.touched : false; }
@@ -24,6 +30,11 @@ export class AbstractControlStatus {
get ngClassValid(): boolean { return this._cd.control ? this._cd.control.valid : false; }
get ngClassInvalid(): boolean { return this._cd.control ? this._cd.control.invalid : false; }
get ngClassPending(): boolean { return this._cd.control ? this._cd.control.pending : false; }
+ get ngClassSubmitted(): boolean {
+ return this._controlContainer && this._controlContainer.formDirective ?
+ (this._controlContainer.formDirective as NgForm | FormGroupDirective).submitted :
+ false;
+ }
}
export const ngControlStatusHost = {
@@ -34,6 +45,7 @@ export const ngControlStatusHost = {
'[class.ng-valid]': 'ngClassValid',
'[class.ng-invalid]': 'ngClassInvalid',
'[class.ng-pending]': 'ngClassPending',
+ '[class.ng-submitted]': 'ngClassSubmitted',
};
/**
@@ -61,14 +73,16 @@ export const ngControlStatusHost = {
*/
@Directive({selector: '[formControlName],[ngModel],[formControl]', host: ngControlStatusHost})
export class NgControlStatus extends AbstractControlStatus {
- constructor(@Self() cd: NgControl) { super(cd); }
+ constructor(@Self() cd: NgControl, @Optional() controlContainer?: ControlContainer) {
+ super(cd, controlContainer);
+ }
}
/**
* @description
* Directive automatically applied to Angular form groups that sets CSS classes
* based on control status (valid/invalid/dirty/etc).
- *
+ *
* @see `NgControlStatus`
*
* @ngModule ReactiveFormsModule
@@ -81,5 +95,5 @@ export class NgControlStatus extends AbstractControlStatus {
host: ngControlStatusHost
})
export class NgControlStatusGroup extends AbstractControlStatus {
- constructor(@Self() cd: ControlContainer) { super(cd); }
+ constructor(@Self() cd: ControlContainer) { super(cd, cd); }
}
diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts
index 9a1b144dda26..e69d6f78efb4 100644
--- a/packages/forms/test/reactive_integration_spec.ts
+++ b/packages/forms/test/reactive_integration_spec.ts
@@ -807,6 +807,14 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
fixture.detectChanges();
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+
+ const formEl = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(formEl, 'submit');
+ fixture.detectChanges();
+
+ expect(sortedClassList(input)).toEqual([
+ 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid'
+ ]);
});
it('should work with formGroup', () => {
@@ -830,6 +838,13 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
fixture.detectChanges();
expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+
+ dispatchEvent(formEl, 'submit');
+ fixture.detectChanges();
+
+ expect(sortedClassList(formEl)).toEqual([
+ 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid'
+ ]);
});
});
diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts
index dcece0fabfef..ad132d146bb2 100644
--- a/packages/forms/test/template_integration_spec.ts
+++ b/packages/forms/test/template_integration_spec.ts
@@ -167,6 +167,14 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
dispatchEvent(input, 'input');
fixture.detectChanges();
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+
+ const form = fixture.debugElement.query(By.css('form')).nativeElement;
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(sortedClassList(input)).toEqual([
+ 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid'
+ ]);
});
}));
@@ -225,6 +233,16 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
+
+ dispatchEvent(form, 'submit');
+ fixture.detectChanges();
+
+ expect(sortedClassList(modelGroup)).toEqual([
+ 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid'
+ ]);
+ expect(sortedClassList(form)).toEqual([
+ 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid'
+ ]);
});
}));
diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts
index 756a1654d423..c99cbb72b958 100644
--- a/tools/public_api_guard/forms/forms.d.ts
+++ b/tools/public_api_guard/forms/forms.d.ts
@@ -358,7 +358,7 @@ export declare abstract class NgControl extends AbstractControlDirective {
}
export declare class NgControlStatus extends AbstractControlStatus {
- constructor(cd: NgControl);
+ constructor(cd: NgControl, controlContainer?: ControlContainer);
}
export declare class NgControlStatusGroup extends AbstractControlStatus {