diff --git a/src/material/autocomplete/autocomplete-trigger.ts b/src/material/autocomplete/autocomplete-trigger.ts index b2cd6c5612e4..85e9860c7e03 100644 --- a/src/material/autocomplete/autocomplete-trigger.ts +++ b/src/material/autocomplete/autocomplete-trigger.ts @@ -112,8 +112,8 @@ export abstract class _MatAutocompleteTriggerBase /** Old value of the native input. Used to work around issues with the `input` event on IE. */ private _previousValue: string | number | null; - /** Value of the input element when the panel was opened. */ - private _valueOnOpen: string | number | null; + /** Value of the input element when the panel was attached (even if there are no options). */ + private _valueOnAttach: string | number | null; /** Strategy that is used to position the panel. */ private _positionStrategy: FlexibleConnectedPositionStrategy; @@ -574,6 +574,7 @@ export abstract class _MatAutocompleteTriggerBase // of the available options, // - if a valid string is entered after an invalid one. if (this.panelOpen) { + this._captureValueOnAttach(); this._emitOpened(); } else { this.autocomplete.closed.emit(); @@ -596,10 +597,14 @@ export abstract class _MatAutocompleteTriggerBase * the state of the trigger right before the opening sequence was finished. */ private _emitOpened() { - this._valueOnOpen = this._element.nativeElement.value; this.autocomplete.opened.emit(); } + /** Intended to be called when the panel is attached. Captures the current value of the input. */ + private _captureValueOnAttach() { + this._valueOnAttach = this._element.nativeElement.value; + } + /** Destroys the autocomplete suggestion panel. */ private _destroyPanel(): void { if (this._overlayRef) { @@ -650,7 +655,10 @@ export abstract class _MatAutocompleteTriggerBase this._onChange(toSelect.value); panel._emitSelectEvent(toSelect); this._element.nativeElement.focus(); - } else if (panel.requireSelection && this._element.nativeElement.value !== this._valueOnOpen) { + } else if ( + panel.requireSelection && + this._element.nativeElement.value !== this._valueOnAttach + ) { this._clearPreviousSelectedOption(null); this._assignOptionValue(null); // Wait for the animation to finish before clearing the form control value, otherwise @@ -712,8 +720,8 @@ export abstract class _MatAutocompleteTriggerBase this.autocomplete._isOpen = this._overlayAttached = true; this.autocomplete._setColor(this._formField?.color); this._updatePanelState(); - this._applyModalPanelOwnership(); + this._captureValueOnAttach(); // We need to do an extra `panelOpen` check in here, because the // autocomplete won't be shown if there are no options. diff --git a/src/material/autocomplete/autocomplete.spec.ts b/src/material/autocomplete/autocomplete.spec.ts index 27365fa5b76f..41e51dd5a3d7 100644 --- a/src/material/autocomplete/autocomplete.spec.ts +++ b/src/material/autocomplete/autocomplete.spec.ts @@ -2663,6 +2663,37 @@ describe('MDC-based MatAutocomplete', () => { expect(spy).not.toHaveBeenCalled(); subscription.unsubscribe(); })); + + it('should preserve the value if a selection is required, and there are no options', fakeAsync(() => { + const input = fixture.nativeElement.querySelector('input'); + const {stateCtrl, trigger, states} = fixture.componentInstance; + fixture.componentInstance.requireSelection = true; + stateCtrl.setValue(states[1]); + fixture.detectChanges(); + tick(); + + expect(input.value).toBe('California'); + expect(stateCtrl.value).toEqual({code: 'CA', name: 'California'}); + + fixture.componentInstance.states = fixture.componentInstance.filteredStates = []; + fixture.detectChanges(); + + trigger.openPanel(); + fixture.detectChanges(); + zone.simulateZoneExit(); + + const spy = jasmine.createSpy('optionSelected spy'); + const subscription = trigger.optionSelections.subscribe(spy); + + dispatchFakeEvent(document, 'click'); + fixture.detectChanges(); + tick(); + + expect(input.value).toBe('California'); + expect(stateCtrl.value).toEqual({code: 'CA', name: 'California'}); + expect(spy).not.toHaveBeenCalled(); + subscription.unsubscribe(); + })); }); describe('panel closing', () => {