Skip to content

Commit

Permalink
fix(material/autocomplete): clear selected option if it is removed wh…
Browse files Browse the repository at this point in the history
…ile typing

Based on an internal bug report. Clears the selected option in the autocomplete if the user types in something else.
  • Loading branch information
crisbeto committed Nov 17, 2023
1 parent 603f550 commit 7539b61
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 6 deletions.
22 changes: 16 additions & 6 deletions src/material/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,18 @@ export class MatAutocompleteTrigger

if (!value) {
this._clearPreviousSelectedOption(null, false);
} else if (this.panelOpen && !this.autocomplete.requireSelection) {
// Note that we don't reset this when `requireSelection` is enabled,
// because the option will be reset when the panel is closed.
const selectedOption = this.autocomplete.options?.find(option => option.selected);

if (selectedOption) {
const display = this.autocomplete.displayWith?.(selectedOption) ?? selectedOption.value;

if (value !== display) {
selectedOption.deselect(false);
}
}
}

if (this._canOpen() && this._document.activeElement === event.target) {
Expand Down Expand Up @@ -640,18 +652,16 @@ export class MatAutocompleteTrigger
? this.autocomplete.displayWith(value)
: value;

if (value == null) {
this._clearPreviousSelectedOption(null, false);
}

// Simply falling back to an empty string if the display value is falsy does not work properly.
// The display value can also be the number zero and shouldn't fall back to an empty string.
this._updateNativeInputValue(toDisplay != null ? toDisplay : '');
}

private _updateNativeInputValue(value: string): void {
// We want to clear the previous selection if our new value is falsy. e.g: reactive form field
// being reset.
if (!value) {
this._clearPreviousSelectedOption(null, false);
}

// If it's used within a `MatFormField`, we should set it through the property so it can go
// through change detection.
if (this._formField) {
Expand Down
59 changes: 59 additions & 0 deletions src/material/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,65 @@ describe('MDC-based MatAutocomplete', () => {
}).not.toThrow();
});

it('should clear the selected option if it no longer matches the input text while typing', fakeAsync(() => {
const fixture = createComponent(SimpleAutocomplete);
fixture.detectChanges();
tick();

fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
zone.simulateZoneExit();

// Select an option and reopen the panel.
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
fixture.detectChanges();
tick();
fixture.detectChanges();
fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
zone.simulateZoneExit();

expect(fixture.componentInstance.options.first.selected).toBe(true);

const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
input.value = '';
typeInElement(input, 'Ala');
fixture.detectChanges();
tick();

expect(fixture.componentInstance.options.first.selected).toBe(false);
}));

it('should not clear the selected option if it no longer matches the input text while typing with requireSelection', fakeAsync(() => {
const fixture = createComponent(SimpleAutocomplete);
fixture.componentInstance.requireSelection = true;
fixture.detectChanges();
tick();

fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
zone.simulateZoneExit();

// Select an option and reopen the panel.
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
fixture.detectChanges();
tick();
fixture.detectChanges();
fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();
zone.simulateZoneExit();

expect(fixture.componentInstance.options.first.selected).toBe(true);

const input = fixture.debugElement.query(By.css('input'))!.nativeElement;
input.value = '';
typeInElement(input, 'Ala');
fixture.detectChanges();
tick();

expect(fixture.componentInstance.options.first.selected).toBe(true);
}));

describe('forms integration', () => {
let fixture: ComponentFixture<SimpleAutocomplete>;
let input: HTMLInputElement;
Expand Down

0 comments on commit 7539b61

Please sign in to comment.