Skip to content

Commit e45ecb9

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 e45ecb9

File tree

2 files changed

+46
-2
lines changed

2 files changed

+46
-2
lines changed

src/lib/autocomplete/autocomplete-trigger.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,15 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, OnDestroy {
201201

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

207215
/** The currently active option, coerced to MatOption type. */

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)