Skip to content

Commit 90c1ecb

Browse files
committed
fix(select): clear select if no option matches value
Closes #2109
1 parent dd508ea commit 90c1ecb

File tree

3 files changed

+80
-16
lines changed

3 files changed

+80
-16
lines changed

src/demo-app/select/select-demo.html

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<p> Status: {{ foodControl.status }} </p>
1313
<button md-button (click)="foodControl.setValue('pizza-1')">SET VALUE</button>
1414
<button md-button (click)="toggleDisabled()">TOGGLE DISABLED</button>
15+
<button md-button (click)="foodControl.reset()">RESET</button>
1516
</md-card>
1617
</div>
1718

@@ -30,6 +31,7 @@
3031
<button md-button (click)="currentDrink='sprite-1'">SET VALUE</button>
3132
<button md-button (click)="isRequired=!isRequired">TOGGLE REQUIRED</button>
3233
<button md-button (click)="isDisabled=!isDisabled">TOGGLE DISABLED</button>
34+
<button md-button (click)="drinkControl.reset()">RESET</button>
3335
</md-card>
3436

3537
</div>

src/lib/select/select.spec.ts

+54-11
Original file line numberDiff line numberDiff line change
@@ -193,19 +193,19 @@ describe('MdSelect', () => {
193193
});
194194

195195
it('should focus the selected option if an option is selected', async(() => {
196-
trigger.click();
197-
fixture.detectChanges();
198-
199-
const options =
200-
overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
201-
options[1].click();
202-
fixture.detectChanges();
196+
// must wait for initial writeValue promise to finish
197+
fixture.whenStable().then(() => {
198+
fixture.componentInstance.control.setValue('pizza-1');
199+
fixture.detectChanges();
203200

204-
trigger.click();
205-
fixture.detectChanges();
201+
trigger.click();
202+
fixture.detectChanges();
206203

207-
fixture.whenStable().then(() => {
208-
expect(fixture.componentInstance.select._keyManager.focusedItemIndex).toEqual(1);
204+
// must wait for animation to finish
205+
fixture.whenStable().then(() => {
206+
fixture.detectChanges();
207+
expect(fixture.componentInstance.select._keyManager.focusedItemIndex).toEqual(1);
208+
});
209209
});
210210
}));
211211

@@ -307,6 +307,49 @@ describe('MdSelect', () => {
307307
.toEqual('steak-0', `Expected control's value to be set to the new option.`);
308308
});
309309

310+
it('should clear the selection when a nonexistent option value is selected', () => {
311+
fixture.componentInstance.control.setValue('pizza-1');
312+
fixture.detectChanges();
313+
314+
fixture.componentInstance.control.setValue('gibberish');
315+
fixture.detectChanges();
316+
317+
const value = fixture.debugElement.query(By.css('.md-select-value'));
318+
expect(value).toBe(null, `Expected trigger to be cleared when option value is not found.`);
319+
expect(trigger.textContent)
320+
.not.toContain('Pizza', `Expected trigger to be cleared when option value is not found.`);
321+
322+
trigger.click();
323+
fixture.detectChanges();
324+
325+
const options =
326+
overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
327+
expect(options[1].classList)
328+
.not.toContain('md-selected', `Expected option with the old value not to be selected.`);
329+
});
330+
331+
332+
it('should clear the selection when the control is reset', () => {
333+
fixture.componentInstance.control.setValue('pizza-1');
334+
fixture.detectChanges();
335+
336+
fixture.componentInstance.control.reset();
337+
fixture.detectChanges();
338+
339+
const value = fixture.debugElement.query(By.css('.md-select-value'));
340+
expect(value).toBe(null, `Expected trigger to be cleared when option value is not found.`);
341+
expect(trigger.textContent)
342+
.not.toContain('Pizza', `Expected trigger to be cleared when option value is not found.`);
343+
344+
trigger.click();
345+
fixture.detectChanges();
346+
347+
const options =
348+
overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
349+
expect(options[1].classList)
350+
.not.toContain('md-selected', `Expected option with the old value not to be selected.`);
351+
});
352+
310353
it('should set the control to touched when the select is touched', () => {
311354
expect(fixture.componentInstance.control.touched)
312355
.toEqual(false, `Expected the control to start off as untouched.`);

src/lib/select/select.ts

+24-5
Original file line numberDiff line numberDiff line change
@@ -277,11 +277,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
277277
return;
278278
}
279279

280-
this.options.forEach((option: MdOption) => {
281-
if (option.value === value) {
282-
option.select();
283-
}
284-
});
280+
this._setSelectionByValue(value);
285281
}
286282

287283
/**
@@ -378,6 +374,28 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
378374
scrollContainer.scrollTop = this._scrollTop;
379375
}
380376

377+
/**
378+
* Sets the selected option based on a value. If no option can be
379+
* found with the designated value, the select trigger is cleared.
380+
*/
381+
private _setSelectionByValue(value: any): void {
382+
this.options.forEach((option: MdOption) => {
383+
if (option.value === value) {
384+
option.select();
385+
}
386+
});
387+
388+
if (this.selected && this.selected.value !== value) {
389+
this._clearSelection();
390+
}
391+
}
392+
393+
/** Clears the select trigger and deselects every option in the list. */
394+
private _clearSelection(): void {
395+
this._selected = null;
396+
this._updateOptions();
397+
}
398+
381399
private _getTriggerRect(): ClientRect {
382400
return this.trigger.nativeElement.getBoundingClientRect();
383401
}
@@ -426,6 +444,7 @@ export class MdSelect implements AfterContentInit, ControlValueAccessor, OnDestr
426444
this._selected = option;
427445
this._updateOptions();
428446
this._setValueWidth();
447+
this._placeholderState = '';
429448
if (this.panelOpen) {
430449
this.close();
431450
}

0 commit comments

Comments
 (0)