From 7fc38b9bdd8fb69e8cc6a00629653f01d0470ea2 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Fri, 13 Jan 2017 23:55:41 +0100 Subject: [PATCH] fix(select): selected option not being highlighted when options are added asynchronously (#2499) * fix(select): selected option not being highlighted when options are added asynchronously Fixes the select component not highlighting the model value when the options get added after initialization. Fixes #2497. * Address the feedback. * Fix linter errors. --- src/lib/select/select.spec.ts | 57 +++++++++++++++++++++++++++++++++-- src/lib/select/select.ts | 10 +++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 8603a2719b4..46ba1602e2d 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -16,7 +16,14 @@ describe('MdSelect', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MdSelectModule.forRoot(), ReactiveFormsModule, FormsModule], - declarations: [BasicSelect, NgModelSelect, ManySelects, NgIfSelect, SelectWithChangeEvent], + declarations: [ + BasicSelect, + NgModelSelect, + ManySelects, + NgIfSelect, + SelectInitWithoutOptions, + SelectWithChangeEvent + ], providers: [ {provide: OverlayContainer, useFactory: () => { overlayContainerElement = document.createElement('div') as HTMLElement; @@ -239,6 +246,27 @@ describe('MdSelect', () => { }); + it('should select the proper option when the list of options is initialized at a later point', + async(() => { + let fixture = TestBed.createComponent(SelectInitWithoutOptions); + let instance = fixture.componentInstance; + + fixture.detectChanges(); + + // Wait for the initial writeValue promise. + fixture.whenStable().then(() => { + expect(instance.select.selected).toBeFalsy(); + + instance.addOptions(); + fixture.detectChanges(); + + // Wait for the next writeValue promise. + fixture.whenStable().then(() => { + expect(instance.select.selected).toBe(instance.options.toArray()[1]); + }); + }); + })); + describe('forms integration', () => { let fixture: ComponentFixture; let trigger: HTMLElement; @@ -1267,7 +1295,6 @@ class ManySelects {} `, - }) class NgIfSelect { isShowing = false; @@ -1304,6 +1331,32 @@ class SelectWithChangeEvent { changeListener = jasmine.createSpy('MdSelect change listener'); } +@Component({ + selector: 'select-init-without-options', + template: ` + + + {{ food.viewValue }} + + + ` +}) +class SelectInitWithoutOptions { + foods: any[]; + control = new FormControl('pizza-1'); + + @ViewChild(MdSelect) select: MdSelect; + @ViewChildren(MdOption) options: QueryList; + + addOptions() { + this.foods = [ + { value: 'steak-0', viewValue: 'Steak' }, + { value: 'pizza-1', viewValue: 'Pizza' }, + { value: 'tacos-2', viewValue: 'Tacos'} + ]; + } +} + /** * TODO: Move this to core testing utility until Angular has event faking * support. diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index cb7d2e7a355..c82cc0fcecb 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -241,7 +241,15 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr ngAfterContentInit() { this._initKeyManager(); this._resetOptions(); - this._changeSubscription = this.options.changes.subscribe(() => this._resetOptions()); + this._changeSubscription = this.options.changes.subscribe(() => { + this._resetOptions(); + + if (this._control) { + // Defer setting the value in order to avoid the "Expression + // has changed after it was checked" errors from Angular. + Promise.resolve(null).then(() => this._setSelectionByValue(this._control.value)); + } + }); } ngOnDestroy() {