Skip to content

Commit 8f999a0

Browse files
committed
fix(select): exception when initialized with large amount of options
Fixes an error being thrown by `mat-select` when it is initialized with a large amount of pre-selected options. The issue comes from the fact that we add a change listener right before the initial values are assigned rather than afterwards. Fixes #12504.
1 parent aa22368 commit 8f999a0

File tree

2 files changed

+42
-9
lines changed

2 files changed

+42
-9
lines changed

src/material/select/select.spec.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2664,8 +2664,9 @@ describe('MatSelect', () => {
26642664

26652665
it('should be able to programmatically select a falsy option', fakeAsync(() => {
26662666
const fixture = TestBed.createComponent(FalsyValueSelect);
2667-
26682667
fixture.detectChanges();
2668+
flush();
2669+
26692670
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
26702671
fixture.componentInstance.control.setValue(0);
26712672
fixture.detectChanges();
@@ -3216,6 +3217,8 @@ describe('MatSelect', () => {
32163217

32173218
let groupFixture = TestBed.createComponent(SelectWithGroups);
32183219
groupFixture.detectChanges();
3220+
flush();
3221+
32193222
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
32203223
formField = groupFixture.debugElement.query(By.css('mat-form-field')).nativeElement;
32213224

@@ -3883,7 +3886,10 @@ describe('MatSelect', () => {
38833886
});
38843887

38853888
describe('with multiple selection', () => {
3886-
beforeEach(async(() => configureMatSelectTestingModule([MultiSelect])));
3889+
beforeEach(async(() => configureMatSelectTestingModule([
3890+
MultiSelect,
3891+
MultiSelectWithLotsOfPreselectedOptions
3892+
])));
38873893

38883894
let fixture: ComponentFixture<MultiSelect>;
38893895
let testInstance: MultiSelect;
@@ -4264,6 +4270,17 @@ describe('MatSelect', () => {
42644270
expect(testInstance.control.value).toEqual([]);
42654271
});
42664272

4273+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4274+
fixture.destroy();
4275+
4276+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4277+
4278+
expect(() => {
4279+
lotsOfOptionsFixture.detectChanges();
4280+
flush();
4281+
}).not.toThrow();
4282+
}));
4283+
42674284
});
42684285
});
42694286

@@ -5038,3 +5055,17 @@ class SelectWithFormFieldLabel {
50385055
class SelectWithNgIfAndLabel {
50395056
showSelect = true;
50405057
}
5058+
5059+
@Component({
5060+
template: `
5061+
<mat-form-field>
5062+
<mat-select multiple [ngModel]="value">
5063+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
5064+
</mat-select>
5065+
</mat-form-field>
5066+
`
5067+
})
5068+
class MultiSelectWithLotsOfPreselectedOptions {
5069+
items = new Array(1000).fill(0).map((_, i) => i);
5070+
value = [...this.items];
5071+
}

src/material/select/select.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ import {
8080
distinctUntilChanged,
8181
filter,
8282
map,
83-
startWith,
8483
switchMap,
8584
take,
8685
takeUntil,
@@ -546,13 +545,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
546545

547546
ngAfterContentInit() {
548547
this._initKeyManager();
548+
this._resetOptions();
549549

550-
this._selectionModel.onChange.pipe(takeUntil(this._destroy)).subscribe(event => {
551-
event.added.forEach(option => option.select());
552-
event.removed.forEach(option => option.deselect());
550+
this._initializeSelection().then(() => {
551+
this._selectionModel.onChange.pipe(takeUntil(this._destroy)).subscribe(event => {
552+
event.added.forEach(option => option.select());
553+
event.removed.forEach(option => option.deselect());
554+
});
553555
});
554556

555-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
557+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
556558
this._resetOptions();
557559
this._initializeSelection();
558560
});
@@ -833,10 +835,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
833835
return !this._selectionModel || this._selectionModel.isEmpty();
834836
}
835837

836-
private _initializeSelection(): void {
838+
private _initializeSelection(): Promise<void> {
837839
// Defer setting the value in order to avoid the "Expression
838840
// has changed after it was checked" errors from Angular.
839-
Promise.resolve().then(() => {
841+
return Promise.resolve().then(() => {
840842
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
841843
this.stateChanges.next();
842844
});

0 commit comments

Comments
 (0)