diff --git a/src/material-experimental/mdc-form-field/form-field.ts b/src/material-experimental/mdc-form-field/form-field.ts index 39a3de844f73..b370d7ad1b66 100644 --- a/src/material-experimental/mdc-form-field/form-field.ts +++ b/src/material-experimental/mdc-form-field/form-field.ts @@ -283,6 +283,12 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck get: () => this._shouldLabelFloat(), }); + // By default, the foundation determines the validity of the text-field from the + // specified native input. Since we don't pass a native input to the foundation because + // abstract form controls are not necessarily consisting of an input, we handle the + // text-field validity through the abstract form-field control state. + this._foundation.isValid = () => !this._control.errorState; + // Initial focus state sync. This happens rarely, but we want to account for // it in case the form-field control has "focused" set to true on init. this._updateFocusState(); diff --git a/src/material-experimental/mdc-input/input.spec.ts b/src/material-experimental/mdc-input/input.spec.ts index 37c75f4ea473..fa355b0d65b2 100644 --- a/src/material-experimental/mdc-input/input.spec.ts +++ b/src/material-experimental/mdc-input/input.spec.ts @@ -830,6 +830,21 @@ describe('MatMdcInput with forms', () => { .toBe('true', 'Expected aria-invalid to be set to "true".'); })); + it('should not reset text-field validity if focus changes for an invalid input', + fakeAsync(() => { + // Mark the control as touched, so that the form-field displays as invalid. + testComponent.formControl.markAsTouched(); + fixture.detectChanges(); + flush(); + + const wrapperEl = containerEl.querySelector('.mdc-text-field')!; + expect(wrapperEl.classList).toContain('mdc-text-field--invalid'); + + dispatchFakeEvent(inputEl, 'focus'); + dispatchFakeEvent(inputEl, 'blur'); + expect(wrapperEl.classList).toContain('mdc-text-field--invalid'); + })); + it('should display an error message when the parent form is submitted', fakeAsync(() => { expect(testComponent.form.submitted).toBe(false, 'Expected form not to have been submitted'); expect(testComponent.formControl.invalid).toBe(true, 'Expected form control to be invalid');