Skip to content

Commit e9956c2

Browse files
committed
fix(autocomplete): handle optionSelections being accessed early
Currently the `MatAutocompleteTrigger` will throw if `optionSelections` is accessed before `ngAfterViewInit`. These changes add a fallback stream that will be replaced once everything is initialized. Fixes #4616.
1 parent 5210b3e commit e9956c2

File tree

2 files changed

+49
-4
lines changed

2 files changed

+49
-4
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {MatFormField} from '@angular/material/form-field';
4242
import {DOCUMENT} from '@angular/common';
4343
import {Observable} from 'rxjs/Observable';
4444
import {Subject} from 'rxjs/Subject';
45+
import {defer} from 'rxjs/observable/defer';
4546
import {fromEvent} from 'rxjs/observable/fromEvent';
4647
import {merge} from 'rxjs/observable/merge';
4748
import {of as observableOf} from 'rxjs/observable/of';
@@ -200,9 +201,17 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
200201
}
201202

202203
/** Stream of autocomplete option selections. */
203-
get optionSelections(): Observable<MatOptionSelectionChange> {
204-
return merge(...this.autocomplete.options.map(option => option.onSelectionChange));
205-
}
204+
optionSelections: Observable<MatOptionSelectionChange> = defer(() => {
205+
if (this.autocomplete && this.autocomplete.options) {
206+
return merge(...this.autocomplete.options.map(option => option.onSelectionChange));
207+
}
208+
209+
// If there are any subscribers before `ngAfterViewInit`, the `autocomplete` will be undefined.
210+
// Return a stream that we'll replace with the real one once everything is in place.
211+
return this._zone.onStable
212+
.asObservable()
213+
.pipe(take(1), switchMap(() => this.optionSelections));
214+
});
206215

207216
/** The currently active option, coerced to MatOption type. */
208217
get activeOption(): MatOption | null {

src/lib/autocomplete/autocomplete.spec.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from '@angular/core';
2222
import {async, ComponentFixture, fakeAsync, inject, TestBed, tick} from '@angular/core/testing';
2323
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
24-
import {MatOption} from '@angular/material/core';
24+
import {MatOption, MatOptionSelectionChange} from '@angular/material/core';
2525
import {MatFormField, MatFormFieldModule} from '@angular/material/form-field';
2626
import {By} from '@angular/platform-browser';
2727
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -1324,6 +1324,42 @@ describe('MatAutocomplete', () => {
13241324
componentOptions.slice(1).forEach(option => expect(option.deselect).not.toHaveBeenCalled());
13251325
});
13261326
}));
1327+
1328+
it('should emit an event when an option is selected', fakeAsync(() => {
1329+
const spy = jasmine.createSpy('option selection spy');
1330+
const subscription = fixture.componentInstance.trigger.optionSelections.subscribe(spy);
1331+
const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;
1332+
option.click();
1333+
fixture.detectChanges();
1334+
1335+
expect(spy).toHaveBeenCalledWith(jasmine.any(MatOptionSelectionChange));
1336+
}));
1337+
1338+
it('should handle `optionSelections` being accessed too early', async(() => {
1339+
fixture.destroy();
1340+
fixture = TestBed.createComponent(SimpleAutocomplete);
1341+
1342+
let spy = jasmine.createSpy('option selection spy');
1343+
let subscription: Subscription;
1344+
1345+
expect(fixture.componentInstance.trigger.autocomplete).toBeFalsy();
1346+
expect(() => {
1347+
subscription = fixture.componentInstance.trigger.optionSelections.subscribe(spy);
1348+
}).not.toThrow();
1349+
1350+
fixture.detectChanges();
1351+
fixture.componentInstance.trigger.openPanel();
1352+
fixture.detectChanges();
1353+
1354+
fixture.whenStable().then(() => {
1355+
const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;
1356+
option.click();
1357+
fixture.detectChanges();
1358+
1359+
expect(spy).toHaveBeenCalledWith(jasmine.any(MatOptionSelectionChange));
1360+
});
1361+
}));
1362+
13271363
});
13281364

13291365
describe('panel closing', () => {

0 commit comments

Comments
 (0)