From 00b1444d122a64a5b6772d694c838ce8b9ca7b7d Mon Sep 17 00:00:00 2001 From: Jessica Janiuk Date: Thu, 3 Jun 2021 17:19:26 -0700 Subject: [PATCH] Revert "feat(forms): add `ng-submitted` class to forms that have been submitted." (#42474) This reverts commit f024d7556081f8913f21761bb8e6aab8d08be110. PR Close #42474 --- aio/content/guide/form-validation.md | 1 - aio/content/guide/forms.md | 2 - goldens/public-api/forms/forms.d.ts | 2 +- .../forms/src/directives/ng_control_status.ts | 24 +-- .../forms/test/reactive_integration_spec.ts | 167 +----------------- .../forms/test/template_integration_spec.ts | 76 -------- 6 files changed, 13 insertions(+), 259 deletions(-) diff --git a/aio/content/guide/form-validation.md b/aio/content/guide/form-validation.md index dd213c526b..688b0b7feb 100644 --- a/aio/content/guide/form-validation.md +++ b/aio/content/guide/form-validation.md @@ -195,7 +195,6 @@ The following classes are currently supported. * `.ng-dirty` * `.ng-untouched` * `.ng-touched` -* `.ng-submitted` (enclosing form element only) In the following example, 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 31905bd432..4da2ceb83c 100644 --- a/aio/content/guide/forms.md +++ b/aio/content/guide/forms.md @@ -314,8 +314,6 @@ Angular sets special CSS classes on the control element to reflect the state, as -Additionally, Angular applies the `ng-submitted` class to `
` elements upon submission. This class does *not* apply to inner controls. - You use these CSS classes to define the styles for your control based on its status. ### Observe control states diff --git a/goldens/public-api/forms/forms.d.ts b/goldens/public-api/forms/forms.d.ts index ed8a0ee336..34271f8e13 100644 --- a/goldens/public-api/forms/forms.d.ts +++ b/goldens/public-api/forms/forms.d.ts @@ -460,7 +460,7 @@ export declare class RadioControlValueAccessor extends ɵangular_packages_forms_ name: string; onChange: () => void; value: any; - constructor(renderer: Renderer2, elementRef: ElementRef, _registry: ɵangular_packages_forms_forms_r, _injector: Injector); + constructor(renderer: Renderer2, elementRef: ElementRef, _registry: ɵangular_packages_forms_forms_q, _injector: Injector); fireUncheck(value: any): void; ngOnDestroy(): void; ngOnInit(): void; diff --git a/packages/forms/src/directives/ng_control_status.ts b/packages/forms/src/directives/ng_control_status.ts index 9cebd419d2..a032484823 100644 --- a/packages/forms/src/directives/ng_control_status.ts +++ b/packages/forms/src/directives/ng_control_status.ts @@ -12,8 +12,7 @@ import {AbstractControlDirective} from './abstract_control_directive'; import {ControlContainer} from './control_container'; import {NgControl} from './ng_control'; -type AnyControlStatus = - 'untouched'|'touched'|'pristine'|'dirty'|'valid'|'invalid'|'pending'|'submitted'; +type AnyControlStatus = 'untouched'|'touched'|'pristine'|'dirty'|'valid'|'invalid'|'pending'; export class AbstractControlStatus { private _cd: AbstractControlDirective|null; @@ -23,17 +22,6 @@ export class AbstractControlStatus { } is(status: AnyControlStatus): boolean { - // Currently with ViewEngine (in AOT mode) it's not possible to use private methods in host - // bindings. - // TODO: once ViewEngine is removed, this function should be refactored: - // - make the `is` method `protected`, so it's not accessible publicly - // - move the `submitted` status logic to the `NgControlStatusGroup` class - // and make it `private` or `protected` too. - if (status === 'submitted') { - // We check for the `submitted` field from `NgForm` and `FormGroupDirective` classes, but - // we avoid instanceof checks to prevent non-tree-shakable references to those types. - return !!(this._cd as unknown as {submitted: boolean} | null)?.submitted; - } return !!this._cd?.control?.[status]; } } @@ -48,11 +36,6 @@ export const ngControlStatusHost = { '[class.ng-pending]': 'is("pending")', }; -export const ngGroupStatusHost = { - ...ngControlStatusHost, - '[class.ng-submitted]': 'is("submitted")', -}; - /** * @description * Directive automatically applied to Angular form controls that sets CSS classes @@ -86,8 +69,7 @@ export class NgControlStatus extends AbstractControlStatus { /** * @description * Directive automatically applied to Angular form groups that sets CSS classes - * based on control status (valid/invalid/dirty/etc). On groups, this includes the additional - * class ng-submitted. + * based on control status (valid/invalid/dirty/etc). * * @see `NgControlStatus` * @@ -98,7 +80,7 @@ export class NgControlStatus extends AbstractControlStatus { @Directive({ selector: '[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]', - host: ngGroupStatusHost + host: ngControlStatusHost }) export class NgControlStatusGroup extends AbstractControlStatus { constructor(@Optional() @Self() cd: ControlContainer) { diff --git a/packages/forms/test/reactive_integration_spec.ts b/packages/forms/test/reactive_integration_spec.ts index 7e42404d6c..b75e4c3e13 100644 --- a/packages/forms/test/reactive_integration_spec.ts +++ b/packages/forms/test/reactive_integration_spec.ts @@ -190,7 +190,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); }); it('should update nested form group model when UI changes', () => { - const fixture = initTest(NestedFormGroupNameComp); + const fixture = initTest(NestedFormGroupComp); fixture.componentInstance.form = new FormGroup( {'signin': new FormGroup({'login': new FormControl(), 'password': new FormControl()})}); fixture.detectChanges(); @@ -242,7 +242,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); }); it('should pick up dir validators from nested form groups', () => { - const fixture = initTest(NestedFormGroupNameComp, LoginIsEmptyValidator); + const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator); const form = new FormGroup({ 'signin': new FormGroup({'login': new FormControl(''), 'password': new FormControl('')}) }); @@ -260,7 +260,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); }); it('should strip named controls that are not found', () => { - const fixture = initTest(NestedFormGroupNameComp, LoginIsEmptyValidator); + const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator); const form = new FormGroup({ 'signin': new FormGroup({'login': new FormControl(''), 'password': new FormControl('')}) }); @@ -335,7 +335,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); }); it('should attach dirs to all child controls when group control changes', () => { - const fixture = initTest(NestedFormGroupNameComp, LoginIsEmptyValidator); + const fixture = initTest(NestedFormGroupComp, LoginIsEmptyValidator); const form = new FormGroup({ signin: new FormGroup( {login: new FormControl('oldLogin'), password: new FormControl('oldPassword')}) @@ -1087,8 +1087,6 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); fixture.detectChanges(); const input = fixture.debugElement.query(By.css('input')).nativeElement; - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']); dispatchEvent(input, 'blur'); @@ -1101,19 +1099,6 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); fixture.detectChanges(); expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(input)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(input)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); }); it('should work with formGroup', () => { @@ -1137,126 +1122,6 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); fixture.detectChanges(); expect(sortedClassList(formEl)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']); - - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(formEl)).toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(input)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - }); - - it('should not assign `ng-submitted` class to elements with `formArrayName`', () => { - // Since element with the `formArrayName` can not represent top-level forms (can only be - // inside other elements), this test verifies that these elements never receive - // `ng-submitted` CSS class even when they are located inside submitted form. - const fixture = initTest(FormArrayComp); - const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); - const form = new FormGroup({cities: cityArray}); - fixture.componentInstance.form = form; - fixture.componentInstance.cityArray = cityArray; - fixture.detectChanges(); - - const [loginInput, passwordInput] = - fixture.debugElement.queryAll(By.css('input')).map(el => el.nativeElement); - const arrEl = fixture.debugElement.query(By.css('div')).nativeElement; - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - - expect(passwordInput).toBeDefined(); - expect(sortedClassList(loginInput)).not.toContain('ng-submitted'); - expect(sortedClassList(arrEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(loginInput)).not.toContain('ng-submitted'); - expect(sortedClassList(arrEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(loginInput)).not.toContain('ng-submitted'); - expect(sortedClassList(arrEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - }); - - it('should apply submitted status with nested formArrayName', () => { - const fixture = initTest(NestedFormArrayNameComp); - const ic = new FormControl('foo'); - const arr = new FormArray([ic]); - const form = new FormGroup({arr}); - fixture.componentInstance.form = form; - fixture.detectChanges(); - - const input = fixture.debugElement.query(By.css('input')).nativeElement; - const arrEl = fixture.debugElement.query(By.css('div')).nativeElement; - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - - expect(sortedClassList(input)).not.toContain('ng-submitted'); - expect(sortedClassList(arrEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(input)).not.toContain('ng-submitted'); - expect(sortedClassList(arrEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(input)).not.toContain('ng-submitted'); - expect(sortedClassList(arrEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - }); - - it('should apply submitted status with nested formGroupName', () => { - const fixture = initTest(NestedFormGroupNameComp); - const loginControl = - new FormControl('', {validators: Validators.required, updateOn: 'change'}); - const passwordControl = new FormControl('', Validators.required); - const formGroup = new FormGroup( - {signin: new FormGroup({login: loginControl, password: passwordControl})}, - {updateOn: 'blur'}); - fixture.componentInstance.form = formGroup; - fixture.detectChanges(); - - const [loginInput, passwordInput] = - fixture.debugElement.queryAll(By.css('input')).map(el => el.nativeElement); - - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - const groupEl = fixture.debugElement.query(By.css('div')).nativeElement; - loginInput.value = 'Nancy'; - // Input and blur events, as in a real interaction, cause the form to be touched and - // dirtied. - dispatchEvent(loginInput, 'input'); - dispatchEvent(loginInput, 'blur'); - fixture.detectChanges(); - - expect(sortedClassList(loginInput)).not.toContain('ng-submitted'); - expect(sortedClassList(groupEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); - - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(loginInput)).not.toContain('ng-submitted'); - expect(sortedClassList(groupEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(loginInput)).not.toContain('ng-submitted'); - expect(sortedClassList(groupEl)).not.toContain('ng-submitted'); - expect(sortedClassList(formEl)).not.toContain('ng-submitted'); }); }); @@ -1636,7 +1501,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); it('should allow child control updateOn blur to override group updateOn', () => { - const fixture = initTest(NestedFormGroupNameComp); + const fixture = initTest(NestedFormGroupComp); const loginControl = new FormControl('', {validators: Validators.required, updateOn: 'change'}); const passwordControl = new FormControl('', Validators.required); @@ -1945,7 +1810,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); const validatorSpy = jasmine.createSpy('validator'); const groupValidatorSpy = jasmine.createSpy('groupValidatorSpy'); - const fixture = initTest(NestedFormGroupNameComp); + const fixture = initTest(NestedFormGroupComp); const formGroup = new FormGroup({ signin: new FormGroup({login: new FormControl(), password: new FormControl()}), email: new FormControl('', {updateOn: 'submit'}) @@ -2042,7 +1907,7 @@ const ValueAccessorB = createControlValueAccessor('[cva-b]'); }); it('should allow child control updateOn submit to override group updateOn', () => { - const fixture = initTest(NestedFormGroupNameComp); + const fixture = initTest(NestedFormGroupComp); const loginControl = new FormControl('', {validators: Validators.required, updateOn: 'change'}); const passwordControl = new FormControl('', Validators.required); @@ -4942,7 +4807,7 @@ class FormGroupComp { } @Component({ - selector: 'nested-form-group-name-comp', + selector: 'nested-form-group-comp', template: `
@@ -4952,7 +4817,7 @@ class FormGroupComp { ` }) -class NestedFormGroupNameComp { +class NestedFormGroupComp { // TODO(issue/24571): remove '!'. form!: FormGroup; } @@ -4975,20 +4840,6 @@ class FormArrayComp { cityArray!: FormArray; } -@Component({ - selector: 'nested-form-array-name-comp', - template: ` -
-
- -
-
- ` -}) -class NestedFormArrayNameComp { - form!: FormGroup; -} - @Component({ selector: 'form-array-nested-group', template: ` diff --git a/packages/forms/test/template_integration_spec.ts b/packages/forms/test/template_integration_spec.ts index f31adfbb23..8270270ceb 100644 --- a/packages/forms/test/template_integration_spec.ts +++ b/packages/forms/test/template_integration_spec.ts @@ -187,21 +187,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat dispatchEvent(input, 'input'); 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(formEl)).toEqual([ - 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid' - ]); - expect(sortedClassList(input)).not.toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(formEl)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']); - expect(sortedClassList(input)).not.toContain('ng-submitted'); }); })); @@ -259,52 +244,9 @@ 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']); - - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(formEl)).toEqual([ - 'ng-dirty', 'ng-submitted', 'ng-touched', 'ng-valid' - ]); }); })); - it('should set status classes involving nested FormGroups', () => { - const fixture = initTest(NgModelNestedForm); - fixture.componentInstance.first = ''; - fixture.componentInstance.other = ''; - fixture.detectChanges(); - - const form = fixture.debugElement.query(By.css('form')).nativeElement; - const modelGroup = fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement; - const input = fixture.debugElement.query(By.css('input')).nativeElement; - - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(sortedClassList(modelGroup)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']); - - expect(sortedClassList(form)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']); - - const formEl = fixture.debugElement.query(By.css('form')).nativeElement; - dispatchEvent(formEl, 'submit'); - fixture.detectChanges(); - - expect(sortedClassList(modelGroup)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']); - expect(sortedClassList(form)).toEqual([ - 'ng-pristine', 'ng-submitted', 'ng-untouched', 'ng-valid' - ]); - expect(sortedClassList(input)).not.toContain('ng-submitted'); - - dispatchEvent(formEl, 'reset'); - fixture.detectChanges(); - - expect(sortedClassList(modelGroup)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']); - expect(sortedClassList(form)).toEqual(['ng-pristine', 'ng-untouched', 'ng-valid']); - expect(sortedClassList(input)).not.toContain('ng-submitted'); - }); - }); - it('should not create a template-driven form when ngNoForm is used', () => { const fixture = initTest(NgNoFormComp); fixture.detectChanges(); @@ -2425,24 +2367,6 @@ class NgModelNgIfForm { email!: string; } -@Component({ - selector: 'ng-model-nested', - template: ` -
-
- -
- -
-
-
- ` -}) -class NgModelNestedForm { - first!: string; - other!: string; -} - @Component({ selector: 'ng-no-form', template: `