From 7b47083b1b33e8d835fc6c95fd1904acc1942022 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Thu, 16 Feb 2017 22:41:15 +0100 Subject: [PATCH] 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. --- src/demo-app/select/select-demo.html | 2 + src/demo-app/select/select-demo.ts | 1 + src/lib/select/select.spec.ts | 95 +++++++++++++++++++++++++++- src/lib/select/select.ts | 12 +++- 4 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index 7b9c87a3ccfe..904cc52d4ecc 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -6,6 +6,8 @@ + #drinkControl="ngModel"> + None {{ drink.viewValue }} diff --git a/src/demo-app/select/select-demo.ts b/src/demo-app/select/select-demo.ts index 6e2ff635a202..089fd9a4eb31 100644 --- a/src/demo-app/select/select-demo.ts +++ b/src/demo-app/select/select-demo.ts @@ -23,6 +23,7 @@ export class SelectDemo { pokemonTheme = 'primary'; foods = [ + {value: null, viewValue: 'None'}, {value: 'steak-0', viewValue: 'Steak'}, {value: 'pizza-1', viewValue: 'Pizza'}, {value: 'tacos-2', viewValue: 'Tacos'} diff --git a/src/lib/select/select.spec.ts b/src/lib/select/select.spec.ts index 43a4af6673cf..141f2fdb7b14 100644 --- a/src/lib/select/select.spec.ts +++ b/src/lib/select/select.spec.ts @@ -56,7 +56,8 @@ describe('MdSelect', () => { SelectEarlyAccessSibling, BasicSelectInitiallyHidden, BasicSelectNoPlaceholder, - BasicSelectWithTheming + BasicSelectWithTheming, + ResetValuesSelect ], providers: [ {provide: OverlayContainer, useFactory: () => { @@ -1995,6 +1996,72 @@ describe('MdSelect', () => { }); + + describe('reset values', () => { + let fixture: ComponentFixture; + let trigger: HTMLElement; + let placeholder: HTMLElement; + let options: NodeListOf; + + beforeEach(() => { + fixture = TestBed.createComponent(ResetValuesSelect); + fixture.detectChanges(); + trigger = fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement; + placeholder = fixture.debugElement.query(By.css('.mat-select-placeholder')).nativeElement; + + trigger.click(); + fixture.detectChanges(); + options = overlayContainerElement.querySelectorAll('md-option') as NodeListOf; + + options[0].click(); + fixture.detectChanges(); + }); + + it('should reset when an option with an undefined value is selected', () => { + options[4].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.control.value).toBeUndefined(); + expect(fixture.componentInstance.select.selected).toBeFalsy(); + expect(placeholder.classList).not.toContain('mat-floating-placeholder'); + expect(trigger.textContent).not.toContain('Undefined'); + }); + + it('should reset when an option with a null value is selected', () => { + options[5].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.control.value).toBeNull(); + expect(fixture.componentInstance.select.selected).toBeFalsy(); + expect(placeholder.classList).not.toContain('mat-floating-placeholder'); + expect(trigger.textContent).not.toContain('Null'); + }); + + it('should not reset when any other falsy option is selected', () => { + options[3].click(); + fixture.detectChanges(); + + expect(fixture.componentInstance.control.value).toBe(false); + expect(fixture.componentInstance.select.selected).toBeTruthy(); + expect(placeholder.classList).toContain('mat-floating-placeholder'); + expect(trigger.textContent).toContain('Falsy'); + }); + + it('should not consider the reset values as selected when resetting the form control', () => { + expect(placeholder.classList).toContain('mat-floating-placeholder'); + + fixture.componentInstance.control.reset(); + fixture.detectChanges(); + + expect(fixture.componentInstance.control.value).toBeNull(); + expect(fixture.componentInstance.select.selected).toBeFalsy(); + expect(placeholder.classList).not.toContain('mat-floating-placeholder'); + expect(trigger.textContent).not.toContain('Null'); + expect(trigger.textContent).not.toContain('Undefined'); + }); + + }); + }); @@ -2339,3 +2406,29 @@ class BasicSelectWithTheming { @ViewChild(MdSelect) select: MdSelect; theme: string; } + +@Component({ + selector: 'reset-values-select', + template: ` + + + {{ food.viewValue }} + + + ` +}) +class ResetValuesSelect { + foods: any[] = [ + { value: 'steak-0', viewValue: 'Steak' }, + { value: 'pizza-1', viewValue: 'Pizza' }, + { value: 'tacos-2', viewValue: 'Tacos' }, + { value: false, viewValue: 'Falsy' }, + { viewValue: 'Undefined' }, + { value: null, viewValue: 'Null' }, + ]; + control = new FormControl(); + isRequired: boolean; + + @ViewChild(MdSelect) select: MdSelect; + @ViewChildren(MdOption) options: QueryList; +} diff --git a/src/lib/select/select.ts b/src/lib/select/select.ts index 5099709a42f6..0007df4d0b97 100644 --- a/src/lib/select/select.ts +++ b/src/lib/select/select.ts @@ -567,7 +567,7 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal */ private _selectValue(value: any): MdOption { let optionsArray = this.options.toArray(); - let correspondingOption = optionsArray.find(option => option.value === value); + let correspondingOption = optionsArray.find(option => option.value && option.value === value); if (correspondingOption) { correspondingOption.select(); @@ -632,8 +632,14 @@ export class MdSelect implements AfterContentInit, OnDestroy, OnInit, ControlVal wasSelected ? option.deselect() : option.select(); this._sortValues(); } else { - this._clearSelection(option); - this._selectionModel.select(option); + if (option.value == null) { + this._clearSelection(); + this._onChange(option.value); + this.change.emit(new MdSelectChange(this, option.value)); + } else { + this._clearSelection(option); + this._selectionModel.select(option); + } } if (wasSelected !== this._selectionModel.isSelected(option)) {