Skip to content

Commit 38ebf49

Browse files
committed
fix(material/autocomplete): requireSelection incorrectly resetting value when there are no options (#27781)
The autocomplete has a check not to reset the value if the user didn't interact with the input. The problem was that we only accounted for it when there are options, because while technically an autocomplete is _attached_ when the user focuses, we don't consider it _open_ until it shows some options. Fixes #27767. (cherry picked from commit db06fa8)
1 parent 0fe05b3 commit 38ebf49

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ export abstract class _MatAutocompleteTriggerBase
112112
/** Old value of the native input. Used to work around issues with the `input` event on IE. */
113113
private _previousValue: string | number | null;
114114

115-
/** Value of the input element when the panel was opened. */
116-
private _valueOnOpen: string | number | null;
115+
/** Value of the input element when the panel was attached (even if there are no options). */
116+
private _valueOnAttach: string | number | null;
117117

118118
/** Strategy that is used to position the panel. */
119119
private _positionStrategy: FlexibleConnectedPositionStrategy;
@@ -574,6 +574,7 @@ export abstract class _MatAutocompleteTriggerBase
574574
// of the available options,
575575
// - if a valid string is entered after an invalid one.
576576
if (this.panelOpen) {
577+
this._captureValueOnAttach();
577578
this._emitOpened();
578579
} else {
579580
this.autocomplete.closed.emit();
@@ -596,10 +597,14 @@ export abstract class _MatAutocompleteTriggerBase
596597
* the state of the trigger right before the opening sequence was finished.
597598
*/
598599
private _emitOpened() {
599-
this._valueOnOpen = this._element.nativeElement.value;
600600
this.autocomplete.opened.emit();
601601
}
602602

603+
/** Intended to be called when the panel is attached. Captures the current value of the input. */
604+
private _captureValueOnAttach() {
605+
this._valueOnAttach = this._element.nativeElement.value;
606+
}
607+
603608
/** Destroys the autocomplete suggestion panel. */
604609
private _destroyPanel(): void {
605610
if (this._overlayRef) {
@@ -650,7 +655,10 @@ export abstract class _MatAutocompleteTriggerBase
650655
this._onChange(toSelect.value);
651656
panel._emitSelectEvent(toSelect);
652657
this._element.nativeElement.focus();
653-
} else if (panel.requireSelection && this._element.nativeElement.value !== this._valueOnOpen) {
658+
} else if (
659+
panel.requireSelection &&
660+
this._element.nativeElement.value !== this._valueOnAttach
661+
) {
654662
this._clearPreviousSelectedOption(null);
655663
this._assignOptionValue(null);
656664
// Wait for the animation to finish before clearing the form control value, otherwise
@@ -712,8 +720,8 @@ export abstract class _MatAutocompleteTriggerBase
712720
this.autocomplete._isOpen = this._overlayAttached = true;
713721
this.autocomplete._setColor(this._formField?.color);
714722
this._updatePanelState();
715-
716723
this._applyModalPanelOwnership();
724+
this._captureValueOnAttach();
717725

718726
// We need to do an extra `panelOpen` check in here, because the
719727
// autocomplete won't be shown if there are no options.

src/material/autocomplete/autocomplete.spec.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2663,6 +2663,37 @@ describe('MDC-based MatAutocomplete', () => {
26632663
expect(spy).not.toHaveBeenCalled();
26642664
subscription.unsubscribe();
26652665
}));
2666+
2667+
it('should preserve the value if a selection is required, and there are no options', fakeAsync(() => {
2668+
const input = fixture.nativeElement.querySelector('input');
2669+
const {stateCtrl, trigger, states} = fixture.componentInstance;
2670+
fixture.componentInstance.requireSelection = true;
2671+
stateCtrl.setValue(states[1]);
2672+
fixture.detectChanges();
2673+
tick();
2674+
2675+
expect(input.value).toBe('California');
2676+
expect(stateCtrl.value).toEqual({code: 'CA', name: 'California'});
2677+
2678+
fixture.componentInstance.states = fixture.componentInstance.filteredStates = [];
2679+
fixture.detectChanges();
2680+
2681+
trigger.openPanel();
2682+
fixture.detectChanges();
2683+
zone.simulateZoneExit();
2684+
2685+
const spy = jasmine.createSpy('optionSelected spy');
2686+
const subscription = trigger.optionSelections.subscribe(spy);
2687+
2688+
dispatchFakeEvent(document, 'click');
2689+
fixture.detectChanges();
2690+
tick();
2691+
2692+
expect(input.value).toBe('California');
2693+
expect(stateCtrl.value).toEqual({code: 'CA', name: 'California'});
2694+
expect(spy).not.toHaveBeenCalled();
2695+
subscription.unsubscribe();
2696+
}));
26662697
});
26672698

26682699
describe('panel closing', () => {

0 commit comments

Comments
 (0)