Skip to content

Commit 2a19d34

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 1e1751f commit 2a19d34

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

24802480
it('should be able to programmatically select a falsy option', fakeAsync(() => {
24812481
const fixture = TestBed.createComponent(FalsyValueSelect);
2482-
24832482
fixture.detectChanges();
2483+
flush();
2484+
24842485
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
24852486
fixture.componentInstance.control.setValue(0);
24862487
fixture.detectChanges();
@@ -3031,6 +3032,8 @@ describe('MatSelect', () => {
30313032

30323033
let groupFixture = TestBed.createComponent(SelectWithGroups);
30333034
groupFixture.detectChanges();
3035+
flush();
3036+
30343037
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
30353038
formField = groupFixture.debugElement.query(By.css('mat-form-field')).nativeElement;
30363039

@@ -3697,7 +3700,10 @@ describe('MatSelect', () => {
36973700
});
36983701

36993702
describe('with multiple selection', () => {
3700-
beforeEach(async(() => configureMatSelectTestingModule([MultiSelect])));
3703+
beforeEach(async(() => configureMatSelectTestingModule([
3704+
MultiSelect,
3705+
MultiSelectWithLotsOfPreselectedOptions
3706+
])));
37013707

37023708
let fixture: ComponentFixture<MultiSelect>;
37033709
let testInstance: MultiSelect;
@@ -4051,6 +4057,17 @@ describe('MatSelect', () => {
40514057
expect(testInstance.control.value).toEqual([]);
40524058
});
40534059

4060+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4061+
fixture.destroy();
4062+
4063+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4064+
4065+
expect(() => {
4066+
lotsOfOptionsFixture.detectChanges();
4067+
flush();
4068+
}).not.toThrow();
4069+
}));
4070+
40544071
});
40554072
});
40564073

@@ -4806,3 +4823,18 @@ class SelectWithoutOptionCentering {
48064823
class SelectWithFormFieldLabel {
48074824
placeholder: string;
48084825
}
4826+
4827+
4828+
@Component({
4829+
template: `
4830+
<mat-form-field>
4831+
<mat-select multiple [ngModel]="value">
4832+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
4833+
</mat-select>
4834+
</mat-form-field>
4835+
`
4836+
})
4837+
class MultiSelectWithLotsOfPreselectedOptions {
4838+
items = new Array(1000).fill(0).map((_, i) => i);
4839+
value = [...this.items];
4840+
}

src/lib/select/select.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ import {defer, merge, Observable, Subject} from 'rxjs';
7979
import {
8080
filter,
8181
map,
82-
startWith,
8382
switchMap,
8483
take,
8584
takeUntil,
@@ -519,13 +518,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
519518

520519
ngAfterContentInit() {
521520
this._initKeyManager();
521+
this._resetOptions();
522522

523-
this._selectionModel.onChange!.pipe(takeUntil(this._destroy)).subscribe(event => {
524-
event.added.forEach(option => option.select());
525-
event.removed.forEach(option => option.deselect());
523+
this._initializeSelection().then(() => {
524+
this._selectionModel.onChange!.pipe(takeUntil(this._destroy)).subscribe(event => {
525+
event.added.forEach(option => option.select());
526+
event.removed.forEach(option => option.deselect());
527+
});
526528
});
527529

528-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
530+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
529531
this._resetOptions();
530532
this._initializeSelection();
531533
});
@@ -779,10 +781,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
779781
return !this._selectionModel || this._selectionModel.isEmpty();
780782
}
781783

782-
private _initializeSelection(): void {
784+
private _initializeSelection(): Promise<void> {
783785
// Defer setting the value in order to avoid the "Expression
784786
// has changed after it was checked" errors from Angular.
785-
Promise.resolve().then(() => {
787+
return Promise.resolve().then(() => {
786788
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
787789
});
788790
}

0 commit comments

Comments
 (0)