Skip to content

Commit 7592bf7

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 e9466a4 commit 7592bf7

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

src/lib/select/select.spec.ts

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

24932493
it('should be able to programmatically select a falsy option', fakeAsync(() => {
24942494
const fixture = TestBed.createComponent(FalsyValueSelect);
2495-
24962495
fixture.detectChanges();
2496+
flush();
2497+
24972498
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
24982499
fixture.componentInstance.control.setValue(0);
24992500
fixture.detectChanges();
@@ -3044,6 +3045,8 @@ describe('MatSelect', () => {
30443045

30453046
let groupFixture = TestBed.createComponent(SelectWithGroups);
30463047
groupFixture.detectChanges();
3048+
flush();
3049+
30473050
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
30483051
formField = groupFixture.debugElement.query(By.css('mat-form-field')).nativeElement;
30493052

@@ -3710,7 +3713,10 @@ describe('MatSelect', () => {
37103713
});
37113714

37123715
describe('with multiple selection', () => {
3713-
beforeEach(async(() => configureMatSelectTestingModule([MultiSelect])));
3716+
beforeEach(async(() => configureMatSelectTestingModule([
3717+
MultiSelect,
3718+
MultiSelectWithLotsOfPreselectedOptions
3719+
])));
37143720

37153721
let fixture: ComponentFixture<MultiSelect>;
37163722
let testInstance: MultiSelect;
@@ -4091,6 +4097,17 @@ describe('MatSelect', () => {
40914097
expect(testInstance.control.value).toEqual([]);
40924098
});
40934099

4100+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4101+
fixture.destroy();
4102+
4103+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4104+
4105+
expect(() => {
4106+
lotsOfOptionsFixture.detectChanges();
4107+
flush();
4108+
}).not.toThrow();
4109+
}));
4110+
40944111
});
40954112
});
40964113

@@ -4846,3 +4863,18 @@ class SelectWithoutOptionCentering {
48464863
class SelectWithFormFieldLabel {
48474864
placeholder: string;
48484865
}
4866+
4867+
4868+
@Component({
4869+
template: `
4870+
<mat-form-field>
4871+
<mat-select multiple [ngModel]="value">
4872+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
4873+
</mat-select>
4874+
</mat-form-field>
4875+
`
4876+
})
4877+
class MultiSelectWithLotsOfPreselectedOptions {
4878+
items = new Array(1000).fill(0).map((_, i) => i);
4879+
value = [...this.items];
4880+
}

src/lib/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,
@@ -526,13 +525,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
526525

527526
ngAfterContentInit() {
528527
this._initKeyManager();
528+
this._resetOptions();
529529

530-
this._selectionModel.onChange.pipe(takeUntil(this._destroy)).subscribe(event => {
531-
event.added.forEach(option => option.select());
532-
event.removed.forEach(option => option.deselect());
530+
this._initializeSelection().then(() => {
531+
this._selectionModel.onChange.pipe(takeUntil(this._destroy)).subscribe(event => {
532+
event.added.forEach(option => option.select());
533+
event.removed.forEach(option => option.deselect());
534+
});
533535
});
534536

535-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
537+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
536538
this._resetOptions();
537539
this._initializeSelection();
538540
});
@@ -801,10 +803,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
801803
return !this._selectionModel || this._selectionModel.isEmpty();
802804
}
803805

804-
private _initializeSelection(): void {
806+
private _initializeSelection(): Promise<void> {
805807
// Defer setting the value in order to avoid the "Expression
806808
// has changed after it was checked" errors from Angular.
807-
Promise.resolve().then(() => {
809+
return Promise.resolve().then(() => {
808810
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
809811
});
810812
}

0 commit comments

Comments
 (0)