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 (#28146)

Based on an internal bug report. Clears the selected option in the autocomplete if the user types in something else.
  • Loading branch information
crisbeto authored Nov 17, 2023
1 parent a6582b5 commit cf868a5
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 cf868a5

Please sign in to comment.