Skip to content

Commit 01209a2

Browse files
committed
fix(material/autocomplete): don't reset active option if list of options changes
Currently we reset the active option whenever the list of items changes, however this means that the user's selection could be lost while they're interacting, if some items get added to the end of the list out of view (e.g. if the options are fetched via polling). These changes address the issue by only resetting the active option when the panel is opened. Fixes #16608.
1 parent 45fae71 commit 01209a2

File tree

3 files changed

+56
-1
lines changed

3 files changed

+56
-1
lines changed

src/material-experimental/mdc-autocomplete/autocomplete.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2256,6 +2256,32 @@ describe('MDC-based MatAutocomplete', () => {
22562256
componentOptions.slice(1).forEach(option => expect(option.deselect).not.toHaveBeenCalled());
22572257
}));
22582258

2259+
it('should not reset the active item if the options list changes while open', fakeAsync(() => {
2260+
fixture.componentInstance.trigger.openPanel();
2261+
fixture.detectChanges();
2262+
zone.simulateZoneExit();
2263+
fixture.detectChanges();
2264+
2265+
const DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
2266+
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
2267+
fixture.detectChanges();
2268+
tick();
2269+
2270+
const classList = overlayContainerElement.querySelector('mat-option')!.classList;
2271+
expect(classList)
2272+
.withContext('Expected first option to be highlighted.')
2273+
.toContain('mat-mdc-option-active');
2274+
2275+
fixture.componentInstance.states.push({code: 'PR', name: 'Puerto Rico'});
2276+
fixture.detectChanges();
2277+
tick();
2278+
fixture.detectChanges();
2279+
2280+
expect(classList)
2281+
.withContext('Expected first option to stay highlighted.')
2282+
.toContain('mat-mdc-option-active');
2283+
}));
2284+
22592285
it('should be able to preselect the first option', fakeAsync(() => {
22602286
fixture.componentInstance.trigger.autocomplete.autoActiveFirstOption = true;
22612287
fixture.componentInstance.trigger.openPanel();
@@ -2276,6 +2302,7 @@ describe('MDC-based MatAutocomplete', () => {
22762302
testComponent.trigger.autocomplete.autoActiveFirstOption = true;
22772303
testComponent.states[0].disabled = true;
22782304
testComponent.states[1].disabled = true;
2305+
fixture.detectChanges();
22792306
testComponent.trigger.openPanel();
22802307
fixture.detectChanges();
22812308
zone.simulateZoneExit();

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,6 @@ export abstract class _MatAutocompleteTriggerBase
527527
// that were created, and flatten it so our stream only emits closing events...
528528
switchMap(() => {
529529
const wasOpen = this.panelOpen;
530-
this._resetActiveItem();
531530
this.autocomplete._setVisibility();
532531
this._changeDetectorRef.detectChanges();
533532

@@ -539,6 +538,7 @@ export abstract class _MatAutocompleteTriggerBase
539538
// can happen if the users opens the panel and there are no options, but the
540539
// options come in slightly later or as a result of the value changing.
541540
if (wasOpen !== this.panelOpen) {
541+
this._resetActiveItem();
542542
this.autocomplete.opened.emit();
543543
}
544544
}
@@ -653,6 +653,7 @@ export abstract class _MatAutocompleteTriggerBase
653653
// We need to do an extra `panelOpen` check in here, because the
654654
// autocomplete won't be shown if there are no options.
655655
if (this.panelOpen && wasOpen !== this.panelOpen) {
656+
this._resetActiveItem();
656657
this.autocomplete.opened.emit();
657658
}
658659
}

src/material/autocomplete/autocomplete.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2241,6 +2241,32 @@ describe('MatAutocomplete', () => {
22412241
componentOptions.slice(1).forEach(option => expect(option.deselect).not.toHaveBeenCalled());
22422242
}));
22432243

2244+
it('should not reset the active item if the options list changes while open', fakeAsync(() => {
2245+
fixture.componentInstance.trigger.openPanel();
2246+
fixture.detectChanges();
2247+
zone.simulateZoneExit();
2248+
fixture.detectChanges();
2249+
2250+
const DOWN_ARROW_EVENT = createKeyboardEvent('keydown', DOWN_ARROW);
2251+
fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
2252+
fixture.detectChanges();
2253+
tick();
2254+
2255+
const classList = overlayContainerElement.querySelector('mat-option')!.classList;
2256+
expect(classList)
2257+
.withContext('Expected first option to be highlighted.')
2258+
.toContain('mat-active');
2259+
2260+
fixture.componentInstance.states.push({code: 'PR', name: 'Puerto Rico'});
2261+
fixture.detectChanges();
2262+
tick();
2263+
fixture.detectChanges();
2264+
2265+
expect(classList)
2266+
.withContext('Expected first option to stay highlighted.')
2267+
.toContain('mat-active');
2268+
}));
2269+
22442270
it('should be able to preselect the first option', fakeAsync(() => {
22452271
fixture.componentInstance.trigger.autocomplete.autoActiveFirstOption = true;
22462272
fixture.componentInstance.trigger.openPanel();
@@ -2261,6 +2287,7 @@ describe('MatAutocomplete', () => {
22612287
testComponent.trigger.autocomplete.autoActiveFirstOption = true;
22622288
testComponent.states[0].disabled = true;
22632289
testComponent.states[1].disabled = true;
2290+
fixture.detectChanges();
22642291
testComponent.trigger.openPanel();
22652292
fixture.detectChanges();
22662293
zone.simulateZoneExit();

0 commit comments

Comments
 (0)