Skip to content

Commit aa0725d

Browse files
committed
fix(select): allow option with undefined or null value to clear selection
Allows for options, with a value of `null` or `undefined`, to clear the select. This is similar to the way the native select works. Fixes #3110. Fixes #2634.
1 parent 09c8404 commit aa0725d

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
<md-select placeholder="Drink" [color]="drinksTheme" [(ngModel)]="currentDrink" [required]="drinksRequired" [disabled]="drinksDisabled"
88
[floatPlaceholder]="floatPlaceholder" #drinkControl="ngModel">
9+
#drinkControl="ngModel">
10+
<md-option>None</md-option>
911
<md-option *ngFor="let drink of drinks" [value]="drink.value" [disabled]="drink.disabled">
1012
{{ drink.viewValue }}
1113
</md-option>

src/demo-app/select/select-demo.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export class SelectDemo {
2323
pokemonTheme = 'primary';
2424

2525
foods = [
26+
{value: null, viewValue: 'None'},
2627
{value: 'steak-0', viewValue: 'Steak'},
2728
{value: 'pizza-1', viewValue: 'Pizza'},
2829
{value: 'tacos-2', viewValue: 'Tacos'}

src/lib/select/select.spec.ts

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ describe('MdSelect', () => {
5656
SelectEarlyAccessSibling,
5757
BasicSelectInitiallyHidden,
5858
BasicSelectNoPlaceholder,
59-
BasicSelectWithTheming
59+
BasicSelectWithTheming,
60+
ResetValuesSelect
6061
],
6162
providers: [
6263
{provide: OverlayContainer, useFactory: () => {
@@ -1995,6 +1996,72 @@ describe('MdSelect', () => {
19951996

19961997
});
19971998

1999+
2000+
describe('reset values', () => {
2001+
let fixture: ComponentFixture<ResetValuesSelect>;
2002+
let trigger: HTMLElement;
2003+
let placeholder: HTMLElement;
2004+
let options: NodeListOf<HTMLElement>;
2005+
2006+
beforeEach(() => {
2007+
fixture = TestBed.createComponent(ResetValuesSelect);
2008+
fixture.detectChanges();
2009+
trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement;
2010+
placeholder = fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement;
2011+
2012+
trigger.click();
2013+
fixture.detectChanges();
2014+
options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf<HTMLElement>;
2015+
2016+
options[0].click();
2017+
fixture.detectChanges();
2018+
});
2019+
2020+
it('should reset when an option with an undefined value is selected', () => {
2021+
options[4].click();
2022+
fixture.detectChanges();
2023+
2024+
expect(fixture.componentInstance.control.value).toBeUndefined();
2025+
expect(fixture.componentInstance.select.selected).toBeFalsy();
2026+
expect(placeholder.classList).not.toContain('mat-floating-placeholder');
2027+
expect(trigger.textContent).not.toContain('Undefined');
2028+
});
2029+
2030+
it('should reset when an option with a null value is selected', () => {
2031+
options[5].click();
2032+
fixture.detectChanges();
2033+
2034+
expect(fixture.componentInstance.control.value).toBeNull();
2035+
expect(fixture.componentInstance.select.selected).toBeFalsy();
2036+
expect(placeholder.classList).not.toContain('mat-floating-placeholder');
2037+
expect(trigger.textContent).not.toContain('Null');
2038+
});
2039+
2040+
it('should not reset when any other falsy option is selected', () => {
2041+
options[3].click();
2042+
fixture.detectChanges();
2043+
2044+
expect(fixture.componentInstance.control.value).toBe(false);
2045+
expect(fixture.componentInstance.select.selected).toBeTruthy();
2046+
expect(placeholder.classList).toContain('mat-floating-placeholder');
2047+
expect(trigger.textContent).toContain('Falsy');
2048+
});
2049+
2050+
it('should not consider the reset values as selected when resetting the form control', () => {
2051+
expect(placeholder.classList).toContain('mat-floating-placeholder');
2052+
2053+
fixture.componentInstance.control.reset();
2054+
fixture.detectChanges();
2055+
2056+
expect(fixture.componentInstance.control.value).toBeNull();
2057+
expect(fixture.componentInstance.select.selected).toBeFalsy();
2058+
expect(placeholder.classList).not.toContain('mat-floating-placeholder');
2059+
expect(trigger.textContent).not.toContain('Null');
2060+
expect(trigger.textContent).not.toContain('Undefined');
2061+
});
2062+
2063+
});
2064+
19982065
});
19992066

20002067

@@ -2339,3 +2406,29 @@ class BasicSelectWithTheming {
23392406
@ViewChild(MdSelect) select: MdSelect;
23402407
theme: string;
23412408
}
2409+
2410+
@Component({
2411+
selector: 'reset-values-select',
2412+
template: `
2413+
<md-select placeholder="Food" [formControl]="control" [required]="isRequired">
2414+
<md-option *ngFor="let food of foods" [value]="food.value">
2415+
{{ food.viewValue }}
2416+
</md-option>
2417+
</md-select>
2418+
`
2419+
})
2420+
class ResetValuesSelect {
2421+
foods: any[] = [
2422+
{ value: 'steak-0', viewValue: 'Steak' },
2423+
{ value: 'pizza-1', viewValue: 'Pizza' },
2424+
{ value: 'tacos-2', viewValue: 'Tacos' },
2425+
{ value: false, viewValue: 'Falsy' },
2426+
{ viewValue: 'Undefined' },
2427+
{ value: null, viewValue: 'Null' },
2428+
];
2429+
control = new FormControl();
2430+
isRequired: boolean;
2431+
2432+
@ViewChild(MdSelect) select: MdSelect;
2433+
@ViewChildren(MdOption) options: QueryList<MdOption>;
2434+
}

src/lib/select/select.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
565565
*/
566566
private _selectValue(value: any): MdOption {
567567
let optionsArray = this.options.toArray();
568-
let correspondingOption = optionsArray.find(option => option.value === value);
568+
let correspondingOption = optionsArray.find(option => option.value && option.value === value);
569569

570570
if (correspondingOption) {
571571
correspondingOption.select();
@@ -630,8 +630,14 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal
630630
wasSelected ? option.deselect() : option.select();
631631
this._sortValues();
632632
} else {
633-
this._clearSelection(option);
634-
this._selectionModel.select(option);
633+
if (option.value == null) {
634+
this._clearSelection();
635+
this._onChange(option.value);
636+
this.change.emit(new MdSelectChange(this, option.value));
637+
} else {
638+
this._clearSelection(option);
639+
this._selectionModel.select(option);
640+
}
635641
}
636642

637643
if (wasSelected !== this._selectionModel.isSelected(option)) {

0 commit comments

Comments
 (0)