Skip to content

Commit 97ebd76

Browse files
crisbetoandrewseguin
authored andcommitted
fix(select): handle optionSelectionChanges being accessed early (#8830)
Along the same lines #8802, `mat-select` will throw if the `optionSelectionChanges` is accessed before the options are initialized. These changes add a fallback that will replace the observable once the options become available.
1 parent 571ef46 commit 97ebd76

File tree

2 files changed

+61
-3
lines changed

2 files changed

+61
-3
lines changed

src/lib/select/select.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ import {
4343
FloatLabelType,
4444
MAT_LABEL_GLOBAL_OPTIONS,
4545
MatOption,
46+
MatOptionSelectionChange,
4647
} from '@angular/material/core';
4748
import {MatFormFieldModule} from '@angular/material/form-field';
4849
import {By} from '@angular/platform-browser';
4950
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
5051
import {map} from 'rxjs/operators/map';
5152
import {Subject} from 'rxjs/Subject';
53+
import {Subscription} from 'rxjs/Subscription';
5254
import {MatSelectModule} from './index';
5355
import {MatSelect} from './select';
5456
import {
@@ -1001,6 +1003,54 @@ describe('MatSelect', () => {
10011003
it('should not throw if triggerValue accessed with no selected value', fakeAsync(() => {
10021004
expect(() => fixture.componentInstance.select.triggerValue).not.toThrow();
10031005
}));
1006+
1007+
it('should emit to `optionSelectionChanges` when an option is selected', fakeAsync(() => {
1008+
trigger.click();
1009+
fixture.detectChanges();
1010+
flush();
1011+
1012+
const spy = jasmine.createSpy('option selection spy');
1013+
const subscription = fixture.componentInstance.select.optionSelectionChanges.subscribe(spy);
1014+
const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;
1015+
option.click();
1016+
fixture.detectChanges();
1017+
flush();
1018+
1019+
expect(spy).toHaveBeenCalledWith(jasmine.any(MatOptionSelectionChange));
1020+
1021+
subscription.unsubscribe();
1022+
}));
1023+
1024+
it('should handle accessing `optionSelectionChanges` before the options are initialized',
1025+
fakeAsync(() => {
1026+
fixture.destroy();
1027+
fixture = TestBed.createComponent(BasicSelect);
1028+
1029+
let spy = jasmine.createSpy('option selection spy');
1030+
let subscription: Subscription;
1031+
1032+
expect(fixture.componentInstance.select.options).toBeFalsy();
1033+
expect(() => {
1034+
subscription = fixture.componentInstance.select.optionSelectionChanges.subscribe(spy);
1035+
}).not.toThrow();
1036+
1037+
fixture.detectChanges();
1038+
trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
1039+
1040+
trigger.click();
1041+
fixture.detectChanges();
1042+
flush();
1043+
1044+
const option = overlayContainerElement.querySelector('mat-option') as HTMLElement;
1045+
option.click();
1046+
fixture.detectChanges();
1047+
flush();
1048+
1049+
expect(spy).toHaveBeenCalledWith(jasmine.any(MatOptionSelectionChange));
1050+
1051+
subscription!.unsubscribe();
1052+
}));
1053+
10041054
});
10051055

10061056
describe('forms integration', () => {

src/lib/select/select.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {filter} from 'rxjs/operators/filter';
2121
import {take} from 'rxjs/operators/take';
2222
import {map} from 'rxjs/operators/map';
23+
import {switchMap} from 'rxjs/operators/switchMap';
2324
import {startWith} from 'rxjs/operators/startWith';
2425
import {takeUntil} from 'rxjs/operators/takeUntil';
2526
import {
@@ -75,6 +76,7 @@ import {MatFormField, MatFormFieldControl} from '@angular/material/form-field';
7576
import {Observable} from 'rxjs/Observable';
7677
import {merge} from 'rxjs/observable/merge';
7778
import {Subject} from 'rxjs/Subject';
79+
import {defer} from 'rxjs/observable/defer';
7880
import {fadeInContent, transformPanel} from './select-animations';
7981
import {
8082
getMatSelectDynamicMultipleError,
@@ -397,9 +399,15 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
397399
private _id: string;
398400

399401
/** Combined stream of all of the child options' change events. */
400-
get optionSelectionChanges(): Observable<MatOptionSelectionChange> {
401-
return merge(...this.options.map(option => option.onSelectionChange));
402-
}
402+
optionSelectionChanges: Observable<MatOptionSelectionChange> = defer(() => {
403+
if (this.options) {
404+
return merge(...this.options.map(option => option.onSelectionChange));
405+
}
406+
407+
return this._ngZone.onStable
408+
.asObservable()
409+
.pipe(take(1), switchMap(() => this.optionSelectionChanges));
410+
});
403411

404412
/** Event emitted when the select has been opened. */
405413
@Output() openedChange: EventEmitter<boolean> = new EventEmitter<boolean>();

0 commit comments

Comments
 (0)