fix(select): allow for null values in HTML select options bound with ngValue

closes #12829
This commit is contained in:
Dzmitry Shylovich 2016-11-12 02:58:43 +03:00 committed by Victor Berchet
parent b55aaf094f
commit 46023e4792
3 changed files with 47 additions and 13 deletions

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core'; import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
import {isPrimitive, looseIdentical} from '../facade/lang'; import {isPrimitive, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_VALUE_ACCESSOR: any = { export const SELECT_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectControlValueAccessor), useExisting: forwardRef(() => SelectControlValueAccessor),
multi: true multi: true
@ -115,8 +115,8 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
/** @internal */ /** @internal */
_getOptionValue(valueString: string): any { _getOptionValue(valueString: string): any {
let key: string = _extractId(valueString); const id: string = _extractId(valueString);
return this._optionMap.has(key) ? this._optionMap.get(key) : valueString; return this._optionMap.has(id) ? this._optionMap.get(id) : valueString;
} }
} }
@ -158,7 +158,7 @@ export class NgSelectOption implements OnDestroy {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value); this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
} }
ngOnDestroy() { ngOnDestroy(): void {
if (this._select) { if (this._select) {
this._select._optionMap.delete(this.id); this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value); this._select.writeValue(this._select.value);

View File

@ -6,13 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core'; import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Provider, Renderer, forwardRef} from '@angular/core';
import {isPrimitive, looseIdentical} from '../facade/lang'; import {isPrimitive, looseIdentical} from '../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_MULTIPLE_VALUE_ACCESSOR = { export const SELECT_MULTIPLE_VALUE_ACCESSOR: Provider = {
provide: NG_VALUE_ACCESSOR, provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor), useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true multi: true
@ -121,8 +121,8 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
/** @internal */ /** @internal */
_getOptionValue(valueString: string): any { _getOptionValue(valueString: string): any {
let key: string = _extractId(valueString); const id: string = _extractId(valueString);
return this._optionMap.has(key) ? this._optionMap.get(key)._value : valueString; return this._optionMap.has(id) ? this._optionMap.get(id)._value : valueString;
} }
} }
@ -180,12 +180,10 @@ export class NgSelectMultipleOption implements OnDestroy {
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected); this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
} }
ngOnDestroy() { ngOnDestroy(): void {
if (this._select) { if (this._select) {
this._select._optionMap.delete(this.id); this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value); this._select.writeValue(this._select.value);
} }
} }
} }
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];

View File

@ -23,7 +23,7 @@ export function main() {
NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName, NgModelRadioForm, NgModelRangeForm, NgModelSelectForm, NgNoFormComp, InvalidNgModelNoName,
NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper, NgModelOptionsStandalone, NgModelCustomComp, NgModelCustomWrapper,
NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator, NgModelValidationBindings, NgModelMultipleValidators, NgAsyncValidator,
NgModelAsyncValidation NgModelAsyncValidation, NgModelSelectWithNullForm
], ],
imports: [FormsModule] imports: [FormsModule]
}); });
@ -699,6 +699,28 @@ export function main() {
expect(select.nativeElement.value).toEqual('2: Object'); expect(select.nativeElement.value).toEqual('2: Object');
expect(secondNYC.nativeElement.selected).toBe(true); expect(secondNYC.nativeElement.selected).toBe(true);
})); }));
it('should work with null option', fakeAsync(() => {
const fixture = TestBed.createComponent(NgModelSelectWithNullForm);
const comp = fixture.componentInstance;
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}];
comp.selectedCity = null;
fixture.detectChanges();
const select = fixture.debugElement.query(By.css('select'));
select.nativeElement.value = '2: Object';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity['name']).toEqual('NYC');
select.nativeElement.value = '0: null';
dispatchEvent(select.nativeElement, 'change');
fixture.detectChanges();
tick();
expect(comp.selectedCity).toEqual(null);
}));
}); });
describe('custom value accessors', () => { describe('custom value accessors', () => {
@ -1078,6 +1100,20 @@ class NgModelSelectForm {
cities: any[] = []; cities: any[] = [];
} }
@Component({
selector: 'ng-model-select-null-form',
template: `
<select [(ngModel)]="selectedCity">
<option *ngFor="let c of cities" [ngValue]="c"> {{c.name}} </option>
<option [ngValue]="null">Unspecified</option>
</select>
`
})
class NgModelSelectWithNullForm {
selectedCity: {[k: string]: string} = {};
cities: any[] = [];
}
@Component({ @Component({
selector: 'ng-model-custom-comp', selector: 'ng-model-custom-comp',
template: ` template: `