Skip to content

Commit db06fa8

Browse files
authored
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.
1 parent bae9539 commit db06fa8

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
@@ -132,8 +132,8 @@ export class MatAutocompleteTrigger
132132
/** Old value of the native input. Used to work around issues with the `input` event on IE. */
133133
private _previousValue: string | number | null;
134134

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

138138
/** Strategy that is used to position the panel. */
139139
private _positionStrategy: FlexibleConnectedPositionStrategy;
@@ -589,6 +589,7 @@ export class MatAutocompleteTrigger
589589
// of the available options,
590590
// - if a valid string is entered after an invalid one.
591591
if (this.panelOpen) {
592+
this._captureValueOnAttach();
592593
this._emitOpened();
593594
} else {
594595
this.autocomplete.closed.emit();
@@ -611,10 +612,14 @@ export class MatAutocompleteTrigger
611612
* the state of the trigger right before the opening sequence was finished.
612613
*/
613614
private _emitOpened() {
614-
this._valueOnOpen = this._element.nativeElement.value;
615615
this.autocomplete.opened.emit();
616616
}
617617

618+
/** Intended to be called when the panel is attached. Captures the current value of the input. */
619+
private _captureValueOnAttach() {
620+
this._valueOnAttach = this._element.nativeElement.value;
621+
}
622+
618623
/** Destroys the autocomplete suggestion panel. */
619624
private _destroyPanel(): void {
620625
if (this._overlayRef) {
@@ -665,7 +670,10 @@ export class MatAutocompleteTrigger
665670
this._onChange(toSelect.value);
666671
panel._emitSelectEvent(toSelect);
667672
this._element.nativeElement.focus();
668-
} else if (panel.requireSelection && this._element.nativeElement.value !== this._valueOnOpen) {
673+
} else if (
674+
panel.requireSelection &&
675+
this._element.nativeElement.value !== this._valueOnAttach
676+
) {
669677
this._clearPreviousSelectedOption(null);
670678
this._assignOptionValue(null);
671679
// Wait for the animation to finish before clearing the form control value, otherwise
@@ -727,8 +735,8 @@ export class MatAutocompleteTrigger
727735
this.autocomplete._isOpen = this._overlayAttached = true;
728736
this.autocomplete._setColor(this._formField?.color);
729737
this._updatePanelState();
730-
731738
this._applyModalPanelOwnership();
739+
this._captureValueOnAttach();
732740

733741
// We need to do an extra `panelOpen` check in here, because the
734742
// 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)