Skip to content

Commit 9845966

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 28e19a7 commit 9845966

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
@@ -2497,8 +2497,9 @@ describe('MatSelect', () => {
24972497

24982498
it('should be able to programmatically select a falsy option', fakeAsync(() => {
24992499
const fixture = TestBed.createComponent(FalsyValueSelect);
2500-
25012500
fixture.detectChanges();
2501+
flush();
2502+
25022503
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
25032504
fixture.componentInstance.control.setValue(0);
25042505
fixture.detectChanges();
@@ -3049,6 +3050,8 @@ describe('MatSelect', () => {
30493050

30503051
let groupFixture = TestBed.createComponent(SelectWithGroups);
30513052
groupFixture.detectChanges();
3053+
flush();
3054+
30523055
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
30533056
formField = groupFixture.debugElement.query(By.css('mat-form-field')).nativeElement;
30543057

@@ -3715,7 +3718,10 @@ describe('MatSelect', () => {
37153718
});
37163719

37173720
describe('with multiple selection', () => {
3718-
beforeEach(async(() => configureMatSelectTestingModule([MultiSelect])));
3721+
beforeEach(async(() => configureMatSelectTestingModule([
3722+
MultiSelect,
3723+
MultiSelectWithLotsOfPreselectedOptions
3724+
])));
37193725

37203726
let fixture: ComponentFixture<MultiSelect>;
37213727
let testInstance: MultiSelect;
@@ -4096,6 +4102,17 @@ describe('MatSelect', () => {
40964102
expect(testInstance.control.value).toEqual([]);
40974103
});
40984104

4105+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4106+
fixture.destroy();
4107+
4108+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4109+
4110+
expect(() => {
4111+
lotsOfOptionsFixture.detectChanges();
4112+
flush();
4113+
}).not.toThrow();
4114+
}));
4115+
40994116
});
41004117
});
41014118

@@ -4851,3 +4868,18 @@ class SelectWithoutOptionCentering {
48514868
class SelectWithFormFieldLabel {
48524869
placeholder: string;
48534870
}
4871+
4872+
4873+
@Component({
4874+
template: `
4875+
<mat-form-field>
4876+
<mat-select multiple [ngModel]="value">
4877+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
4878+
</mat-select>
4879+
</mat-form-field>
4880+
`
4881+
})
4882+
class MultiSelectWithLotsOfPreselectedOptions {
4883+
items = new Array(1000).fill(0).map((_, i) => i);
4884+
value = [...this.items];
4885+
}

src/lib/select/select.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ import {
7979
distinctUntilChanged,
8080
filter,
8181
map,
82-
startWith,
8382
switchMap,
8483
take,
8584
takeUntil,
@@ -525,13 +524,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
525524

526525
ngAfterContentInit() {
527526
this._initKeyManager();
527+
this._resetOptions();
528528

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

534-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
536+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
535537
this._resetOptions();
536538
this._initializeSelection();
537539
});
@@ -799,10 +801,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
799801
return !this._selectionModel || this._selectionModel.isEmpty();
800802
}
801803

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

0 commit comments

Comments
 (0)