Skip to content

Commit bf013e7

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 db4b0cd commit bf013e7

File tree

2 files changed

+42
-9
lines changed

2 files changed

+42
-9
lines changed

src/material/select/select.spec.ts

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

27432743
it('should be able to programmatically select a falsy option', fakeAsync(() => {
27442744
const fixture = TestBed.createComponent(FalsyValueSelect);
2745-
27462745
fixture.detectChanges();
2746+
flush();
2747+
27472748
fixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement.click();
27482749
fixture.componentInstance.control.setValue(0);
27492750
fixture.detectChanges();
@@ -3374,6 +3375,8 @@ describe('MatSelect', () => {
33743375

33753376
let groupFixture = TestBed.createComponent(SelectWithGroups);
33763377
groupFixture.detectChanges();
3378+
flush();
3379+
33773380
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement;
33783381
formField = groupFixture.debugElement.query(By.css('mat-form-field'))!.nativeElement;
33793382

@@ -3423,6 +3426,7 @@ describe('MatSelect', () => {
34233426
// Select an option in the third group, which has a couple of group labels before it.
34243427
groupFixture.componentInstance.control.setValue('vulpix-7');
34253428
groupFixture.detectChanges();
3429+
flush();
34263430

34273431
trigger.click();
34283432
groupFixture.detectChanges();
@@ -4082,7 +4086,8 @@ describe('MatSelect', () => {
40824086
describe('with multiple selection', () => {
40834087
beforeEach(async(() => configureMatSelectTestingModule([
40844088
MultiSelect,
4085-
MultiSelectWithLotsOfOptions
4089+
MultiSelectWithLotsOfOptions,
4090+
MultiSelectWithLotsOfPreselectedOptions
40864091
])));
40874092

40884093
let fixture: ComponentFixture<MultiSelect>;
@@ -4471,6 +4476,16 @@ describe('MatSelect', () => {
44714476

44724477
expect(() => {
44734478
lotsOfOptionsFixture.componentInstance.checkAll();
4479+
flush();
4480+
}).not.toThrow();
4481+
}));
4482+
4483+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4484+
fixture.destroy();
4485+
4486+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4487+
4488+
expect(() => {
44744489
lotsOfOptionsFixture.detectChanges();
44754490
flush();
44764491
}).not.toThrow();
@@ -5313,3 +5328,18 @@ class MultiSelectWithLotsOfOptions {
53135328
this.value = [];
53145329
}
53155330
}
5331+
5332+
5333+
@Component({
5334+
template: `
5335+
<mat-form-field>
5336+
<mat-select multiple [ngModel]="value">
5337+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
5338+
</mat-select>
5339+
</mat-form-field>
5340+
`
5341+
})
5342+
class MultiSelectWithLotsOfPreselectedOptions {
5343+
items = new Array(1000).fill(0).map((_, i) => i);
5344+
value = [...this.items];
5345+
}

src/material/select/select.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,10 @@ import {
9191
distinctUntilChanged,
9292
filter,
9393
map,
94-
startWith,
9594
switchMap,
9695
take,
9796
takeUntil,
97+
startWith,
9898
} from 'rxjs/operators';
9999
import {matSelectAnimations} from './select-animations';
100100
import {
@@ -592,13 +592,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
592592

593593
ngAfterContentInit() {
594594
this._initKeyManager();
595+
this._resetOptions();
595596

596-
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
597-
event.added.forEach(option => option.select());
598-
event.removed.forEach(option => option.deselect());
597+
this._initializeSelection().then(() => {
598+
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
599+
event.added.forEach(option => option.select());
600+
event.removed.forEach(option => option.deselect());
601+
});
599602
});
600603

601-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
604+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
602605
this._resetOptions();
603606
this._initializeSelection();
604607
});
@@ -874,10 +877,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
874877
return !this._selectionModel || this._selectionModel.isEmpty();
875878
}
876879

877-
private _initializeSelection(): void {
880+
private _initializeSelection(): Promise<void> {
878881
// Defer setting the value in order to avoid the "Expression
879882
// has changed after it was checked" errors from Angular.
880-
Promise.resolve().then(() => {
883+
return Promise.resolve().then(() => {
881884
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
882885
this.stateChanges.next();
883886
});

0 commit comments

Comments
 (0)