Skip to content

Commit f2c81d1

Browse files
committed
fix(material/autocomplete): optionSelections not emitting when the list of options changes
Fixes the `MatAutocompleteTrigger.optionSelections` stopping to emit when the list options has been swapped out. Fixes #14777.
1 parent 8ba7148 commit f2c81d1

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,6 +2120,34 @@ describe('MDC-based MatAutocomplete', () => {
21202120
subscription!.unsubscribe();
21212121
}));
21222122

2123+
it('should emit to `optionSelections` if the list of options changes', fakeAsync(() => {
2124+
const spy = jasmine.createSpy('option selection spy');
2125+
const subscription = fixture.componentInstance.trigger.optionSelections.subscribe(spy);
2126+
const openAndSelectFirstOption = () => {
2127+
fixture.detectChanges();
2128+
fixture.componentInstance.trigger.openPanel();
2129+
fixture.detectChanges();
2130+
zone.simulateZoneExit();
2131+
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
2132+
fixture.detectChanges();
2133+
zone.simulateZoneExit();
2134+
};
2135+
2136+
fixture.componentInstance.states = [{code: 'OR', name: 'Oregon'}];
2137+
fixture.detectChanges();
2138+
2139+
openAndSelectFirstOption();
2140+
expect(spy).toHaveBeenCalledTimes(1);
2141+
2142+
fixture.componentInstance.states = [{code: 'WV', name: 'West Virginia'}];
2143+
fixture.detectChanges();
2144+
2145+
openAndSelectFirstOption();
2146+
expect(spy).toHaveBeenCalledTimes(2);
2147+
2148+
subscription!.unsubscribe();
2149+
}));
2150+
21232151
it('should reposition the panel when the amount of options changes', fakeAsync(() => {
21242152
let formField = fixture.debugElement.query(By.css('.mat-mdc-form-field'))!.nativeElement;
21252153
let inputReference = formField.querySelector('.mdc-text-field');

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import {
4747
} from '@angular/material/core';
4848
import {MAT_FORM_FIELD, MatFormField} from '@angular/material/form-field';
4949
import {defer, fromEvent, merge, Observable, of as observableOf, Subject, Subscription} from 'rxjs';
50-
import {delay, filter, map, switchMap, take, tap} from 'rxjs/operators';
50+
import {delay, filter, map, switchMap, take, tap, startWith} from 'rxjs/operators';
5151

5252
import {
5353
_MatAutocompleteBase,
@@ -304,10 +304,15 @@ export abstract class _MatAutocompleteTriggerBase implements ControlValueAccesso
304304
);
305305
}
306306

307-
/** Stream of autocomplete option selections. */
307+
/** Stream of changes to the selection state of the autocomplete options. */
308308
readonly optionSelections: Observable<MatOptionSelectionChange> = defer(() => {
309-
if (this.autocomplete && this.autocomplete.options) {
310-
return merge(...this.autocomplete.options.map(option => option.onSelectionChange));
309+
const options = this.autocomplete ? this.autocomplete.options : null;
310+
311+
if (options) {
312+
return options.changes.pipe(
313+
startWith(options),
314+
switchMap(() => merge(...options.map(option => option.onSelectionChange)))
315+
);
311316
}
312317

313318
// If there are any subscribers before `ngAfterViewInit`, the `autocomplete` will be undefined.
@@ -349,7 +354,7 @@ export abstract class _MatAutocompleteTriggerBase implements ControlValueAccesso
349354

350355
// Implemented as part of ControlValueAccessor.
351356
writeValue(value: any): void {
352-
Promise.resolve(null).then(() => this._setTriggerValue(value));
357+
Promise.resolve().then(() => this._setTriggerValue(value));
353358
}
354359

355360
// Implemented as part of ControlValueAccessor.

src/material/autocomplete/autocomplete.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,34 @@ describe('MatAutocomplete', () => {
21022102
subscription!.unsubscribe();
21032103
}));
21042104

2105+
it('should emit to `optionSelections` if the list of options changes', fakeAsync(() => {
2106+
const spy = jasmine.createSpy('option selection spy');
2107+
const subscription = fixture.componentInstance.trigger.optionSelections.subscribe(spy);
2108+
const openAndSelectFirstOption = () => {
2109+
fixture.detectChanges();
2110+
fixture.componentInstance.trigger.openPanel();
2111+
fixture.detectChanges();
2112+
zone.simulateZoneExit();
2113+
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
2114+
fixture.detectChanges();
2115+
zone.simulateZoneExit();
2116+
};
2117+
2118+
fixture.componentInstance.states = [{code: 'OR', name: 'Oregon'}];
2119+
fixture.detectChanges();
2120+
2121+
openAndSelectFirstOption();
2122+
expect(spy).toHaveBeenCalledTimes(1);
2123+
2124+
fixture.componentInstance.states = [{code: 'WV', name: 'West Virginia'}];
2125+
fixture.detectChanges();
2126+
2127+
openAndSelectFirstOption();
2128+
expect(spy).toHaveBeenCalledTimes(2);
2129+
2130+
subscription!.unsubscribe();
2131+
}));
2132+
21052133
it('should reposition the panel when the amount of options changes', fakeAsync(() => {
21062134
let formField = fixture.debugElement.query(By.css('.mat-form-field'))!.nativeElement;
21072135
let inputReference = formField.querySelector('.mat-form-field-flex');

0 commit comments

Comments
 (0)