Skip to content

Commit 91e35f4

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 ab0f30d commit 91e35f4

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

src/material/select/select.spec.ts

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

27402740
it('should be able to programmatically select a falsy option', fakeAsync(() => {
27412741
const fixture = TestBed.createComponent(FalsyValueSelect);
2742-
27432742
fixture.detectChanges();
2743+
flush();
2744+
27442745
fixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement.click();
27452746
fixture.componentInstance.control.setValue(0);
27462747
fixture.detectChanges();
@@ -3344,6 +3345,8 @@ describe('MatSelect', () => {
33443345

33453346
let groupFixture = TestBed.createComponent(SelectWithGroups);
33463347
groupFixture.detectChanges();
3348+
flush();
3349+
33473350
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement;
33483351
formField = groupFixture.debugElement.query(By.css('mat-form-field'))!.nativeElement;
33493352

@@ -4052,7 +4055,8 @@ describe('MatSelect', () => {
40524055
describe('with multiple selection', () => {
40534056
beforeEach(async(() => configureMatSelectTestingModule([
40544057
MultiSelect,
4055-
MultiSelectWithLotsOfOptions
4058+
MultiSelectWithLotsOfOptions,
4059+
MultiSelectWithLotsOfPreselectedOptions
40564060
])));
40574061

40584062
let fixture: ComponentFixture<MultiSelect>;
@@ -4441,6 +4445,16 @@ describe('MatSelect', () => {
44414445

44424446
expect(() => {
44434447
lotsOfOptionsFixture.componentInstance.checkAll();
4448+
flush();
4449+
}).not.toThrow();
4450+
}));
4451+
4452+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4453+
fixture.destroy();
4454+
4455+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4456+
4457+
expect(() => {
44444458
lotsOfOptionsFixture.detectChanges();
44454459
flush();
44464460
}).not.toThrow();
@@ -5267,3 +5281,18 @@ class MultiSelectWithLotsOfOptions {
52675281
this.value = [];
52685282
}
52695283
}
5284+
5285+
5286+
@Component({
5287+
template: `
5288+
<mat-form-field>
5289+
<mat-select multiple [ngModel]="value">
5290+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
5291+
</mat-select>
5292+
</mat-form-field>
5293+
`
5294+
})
5295+
class MultiSelectWithLotsOfPreselectedOptions {
5296+
items = new Array(1000).fill(0).map((_, i) => i);
5297+
value = [...this.items];
5298+
}

src/material/select/select.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ import {
8585
distinctUntilChanged,
8686
filter,
8787
map,
88-
startWith,
8988
switchMap,
9089
take,
9190
takeUntil,
91+
startWith,
9292
} from 'rxjs/operators';
9393
import {matSelectAnimations} from './select-animations';
9494
import {
@@ -547,13 +547,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
547547

548548
ngAfterContentInit() {
549549
this._initKeyManager();
550+
this._resetOptions();
550551

551-
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
552-
event.added.forEach(option => option.select());
553-
event.removed.forEach(option => option.deselect());
552+
this._initializeSelection().then(() => {
553+
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
554+
event.added.forEach(option => option.select());
555+
event.removed.forEach(option => option.deselect());
556+
});
554557
});
555558

556-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
559+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
557560
this._resetOptions();
558561
this._initializeSelection();
559562
});
@@ -829,10 +832,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
829832
return !this._selectionModel || this._selectionModel.isEmpty();
830833
}
831834

832-
private _initializeSelection(): void {
835+
private _initializeSelection(): Promise<void> {
833836
// Defer setting the value in order to avoid the "Expression
834837
// has changed after it was checked" errors from Angular.
835-
Promise.resolve().then(() => {
838+
return Promise.resolve().then(() => {
836839
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
837840
this.stateChanges.next();
838841
});

0 commit comments

Comments
 (0)