Skip to content

Commit b75a398

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 fa81811 commit b75a398

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

src/material/select/select.spec.ts

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

26872687
it('should be able to programmatically select a falsy option', fakeAsync(() => {
26882688
const fixture = TestBed.createComponent(FalsyValueSelect);
2689-
26902689
fixture.detectChanges();
2690+
flush();
2691+
26912692
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
26922693
fixture.componentInstance.control.setValue(0);
26932694
fixture.detectChanges();
@@ -3238,6 +3239,8 @@ describe('MatSelect', () => {
32383239

32393240
let groupFixture = TestBed.createComponent(SelectWithGroups);
32403241
groupFixture.detectChanges();
3242+
flush();
3243+
32413244
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
32423245
formField = groupFixture.debugElement.query(By.css('mat-form-field')).nativeElement;
32433246

@@ -3905,7 +3908,10 @@ describe('MatSelect', () => {
39053908
});
39063909

39073910
describe('with multiple selection', () => {
3908-
beforeEach(async(() => configureMatSelectTestingModule([MultiSelect])));
3911+
beforeEach(async(() => configureMatSelectTestingModule([
3912+
MultiSelect,
3913+
MultiSelectWithLotsOfPreselectedOptions
3914+
])));
39093915

39103916
let fixture: ComponentFixture<MultiSelect>;
39113917
let testInstance: MultiSelect;
@@ -4286,6 +4292,17 @@ describe('MatSelect', () => {
42864292
expect(testInstance.control.value).toEqual([]);
42874293
});
42884294

4295+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4296+
fixture.destroy();
4297+
4298+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4299+
4300+
expect(() => {
4301+
lotsOfOptionsFixture.detectChanges();
4302+
flush();
4303+
}).not.toThrow();
4304+
}));
4305+
42894306
});
42904307
});
42914308

@@ -5062,3 +5079,17 @@ class SelectWithFormFieldLabel {
50625079
class SelectWithNgIfAndLabel {
50635080
showSelect = true;
50645081
}
5082+
5083+
@Component({
5084+
template: `
5085+
<mat-form-field>
5086+
<mat-select multiple [ngModel]="value">
5087+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
5088+
</mat-select>
5089+
</mat-form-field>
5090+
`
5091+
})
5092+
class MultiSelectWithLotsOfPreselectedOptions {
5093+
items = new Array(1000).fill(0).map((_, i) => i);
5094+
value = [...this.items];
5095+
}

src/material/select/select.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ import {
8080
distinctUntilChanged,
8181
filter,
8282
map,
83-
startWith,
8483
switchMap,
8584
take,
8685
takeUntil,
86+
startWith,
8787
} from 'rxjs/operators';
8888
import {matSelectAnimations} from './select-animations';
8989
import {
@@ -549,13 +549,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
549549

550550
ngAfterContentInit() {
551551
this._initKeyManager();
552+
this._resetOptions();
552553

553-
this._selectionModel.onChange.pipe(takeUntil(this._destroy)).subscribe(event => {
554-
event.added.forEach(option => option.select());
555-
event.removed.forEach(option => option.deselect());
554+
this._initializeSelection().then(() => {
555+
this._selectionModel.onChange.pipe(takeUntil(this._destroy)).subscribe(event => {
556+
event.added.forEach(option => option.select());
557+
event.removed.forEach(option => option.deselect());
558+
});
556559
});
557560

558-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
561+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
559562
this._resetOptions();
560563
this._initializeSelection();
561564
});
@@ -828,10 +831,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
828831
return !this._selectionModel || this._selectionModel.isEmpty();
829832
}
830833

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

0 commit comments

Comments
 (0)