Skip to content

Commit

Permalink
fix(material/autocomplete): requireSelection incorrectly resetting va…
Browse files Browse the repository at this point in the history
…lue 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.
  • Loading branch information
crisbeto committed Sep 12, 2023
1 parent bae9539 commit db06fa8
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 5 deletions.
18 changes: 13 additions & 5 deletions src/material/autocomplete/autocomplete-trigger.ts
Expand Up @@ -132,8 +132,8 @@ export class MatAutocompleteTrigger
/** 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;
Expand Down Expand Up @@ -589,6 +589,7 @@ export class MatAutocompleteTrigger
// 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();
Expand All @@ -611,10 +612,14 @@ export class MatAutocompleteTrigger
* 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) {
Expand Down Expand Up @@ -665,7 +670,10 @@ export class MatAutocompleteTrigger
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
Expand Down Expand Up @@ -727,8 +735,8 @@ export class MatAutocompleteTrigger
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.
Expand Down
31 changes: 31 additions & 0 deletions src/material/autocomplete/autocomplete.spec.ts
Expand Up @@ -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', () => {
Expand Down

0 comments on commit db06fa8

Please sign in to comment.