Skip to content

Commit 038525b

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 7c75d6e commit 038525b

File tree

2 files changed

+41
-9
lines changed

2 files changed

+41
-9
lines changed

src/material/select/select.spec.ts

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

27422742
it('should be able to programmatically select a falsy option', fakeAsync(() => {
27432743
const fixture = TestBed.createComponent(FalsyValueSelect);
2744-
27452744
fixture.detectChanges();
2745+
flush();
2746+
27462747
fixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement.click();
27472748
fixture.componentInstance.control.setValue(0);
27482749
fixture.detectChanges();
@@ -3346,6 +3347,8 @@ describe('MatSelect', () => {
33463347

33473348
let groupFixture = TestBed.createComponent(SelectWithGroups);
33483349
groupFixture.detectChanges();
3350+
flush();
3351+
33493352
trigger = groupFixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement;
33503353
formField = groupFixture.debugElement.query(By.css('mat-form-field'))!.nativeElement;
33513354

@@ -4054,7 +4057,8 @@ describe('MatSelect', () => {
40544057
describe('with multiple selection', () => {
40554058
beforeEach(async(() => configureMatSelectTestingModule([
40564059
MultiSelect,
4057-
MultiSelectWithLotsOfOptions
4060+
MultiSelectWithLotsOfOptions,
4061+
MultiSelectWithLotsOfPreselectedOptions
40584062
])));
40594063

40604064
let fixture: ComponentFixture<MultiSelect>;
@@ -4443,6 +4447,16 @@ describe('MatSelect', () => {
44434447

44444448
expect(() => {
44454449
lotsOfOptionsFixture.componentInstance.checkAll();
4450+
flush();
4451+
}).not.toThrow();
4452+
}));
4453+
4454+
it('should not throw with a large amount of preselected options', fakeAsync(() => {
4455+
fixture.destroy();
4456+
4457+
const lotsOfOptionsFixture = TestBed.createComponent(MultiSelectWithLotsOfPreselectedOptions);
4458+
4459+
expect(() => {
44464460
lotsOfOptionsFixture.detectChanges();
44474461
flush();
44484462
}).not.toThrow();
@@ -5285,3 +5299,18 @@ class MultiSelectWithLotsOfOptions {
52855299
this.value = [];
52865300
}
52875301
}
5302+
5303+
5304+
@Component({
5305+
template: `
5306+
<mat-form-field>
5307+
<mat-select multiple [ngModel]="value">
5308+
<mat-option *ngFor="let item of items" [value]="item">{{item}}</mat-option>
5309+
</mat-select>
5310+
</mat-form-field>
5311+
`
5312+
})
5313+
class MultiSelectWithLotsOfPreselectedOptions {
5314+
items = new Array(1000).fill(0).map((_, i) => i);
5315+
value = [...this.items];
5316+
}

src/material/select/select.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ import {
9090
distinctUntilChanged,
9191
filter,
9292
map,
93-
startWith,
9493
switchMap,
9594
take,
9695
takeUntil,
96+
startWith,
9797
} from 'rxjs/operators';
9898
import {matSelectAnimations} from './select-animations';
9999
import {
@@ -580,13 +580,16 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
580580

581581
ngAfterContentInit() {
582582
this._initKeyManager();
583+
this._resetOptions();
583584

584-
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
585-
event.added.forEach(option => option.select());
586-
event.removed.forEach(option => option.deselect());
585+
this._initializeSelection().then(() => {
586+
this._selectionModel.changed.pipe(takeUntil(this._destroy)).subscribe(event => {
587+
event.added.forEach(option => option.select());
588+
event.removed.forEach(option => option.deselect());
589+
});
587590
});
588591

589-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
592+
this.options.changes.pipe(takeUntil(this._destroy)).subscribe(() => {
590593
this._resetOptions();
591594
this._initializeSelection();
592595
});
@@ -862,10 +865,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
862865
return !this._selectionModel || this._selectionModel.isEmpty();
863866
}
864867

865-
private _initializeSelection(): void {
868+
private _initializeSelection(): Promise<void> {
866869
// Defer setting the value in order to avoid the "Expression
867870
// has changed after it was checked" errors from Angular.
868-
Promise.resolve().then(() => {
871+
return Promise.resolve().then(() => {
869872
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
870873
this.stateChanges.next();
871874
});

0 commit comments

Comments
 (0)