Skip to content

Commit cd8e8ff

Browse files
committed
fix: add support for ngValue on option elements, thereby fixing non-string option values not working for select elements
1 parent 042c40a commit cd8e8ff

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
## Ngrx Forms Changelog
22

3+
<a name="1.0.2"></a>
4+
### 1.0.2
5+
6+
#### Bugfixes
7+
8+
* add support for `ngValue` on `option` elements, thereby fixing non-string option values not working for `select` elements
9+
310
<a name="1.0.1"></a>
411
### 1.0.1
512

src/control/e2e.spec/select.spec.ts

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { Component, Input } from '@angular/core';
2+
import { Component, Input, getDebugNode } from '@angular/core';
33
import { ActionsSubject, Action } from '@ngrx/store';
44
import { Observable } from 'rxjs/Observable';
55
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
@@ -9,6 +9,7 @@ import 'rxjs/add/operator/skip';
99
import { FormControlState, createFormControlState } from '../../state';
1010
import { SetValueAction } from '../../actions';
1111
import { NgrxFormsModule } from '../../module';
12+
import { NgrxSelectControlValueAccessor } from '../../value-accessors';
1213

1314
const SELECT_OPTIONS = ['op1', 'op2'];
1415

@@ -25,12 +26,13 @@ export class SelectComponent {
2526
describe(SelectComponent.name, () => {
2627
let component: SelectComponent;
2728
let fixture: ComponentFixture<SelectComponent>;
29+
let valueAccessor: NgrxSelectControlValueAccessor;
2830
let actionsSubject: ActionsSubject;
2931
let actions$: Observable<Action>;
3032
let element: HTMLSelectElement;
3133
const FORM_CONTROL_ID = 'test ID';
3234
const INITIAL_FORM_CONTROL_VALUE = SELECT_OPTIONS[1];
33-
const INITIAL_STATE = createFormControlState<string>(FORM_CONTROL_ID, INITIAL_FORM_CONTROL_VALUE);
35+
const INITIAL_STATE = createFormControlState(FORM_CONTROL_ID, INITIAL_FORM_CONTROL_VALUE);
3436

3537
beforeEach(() => {
3638
actionsSubject = new BehaviorSubject<Action>({ type: '' }) as ActionsSubject;
@@ -50,11 +52,12 @@ describe(SelectComponent.name, () => {
5052
component = fixture.componentInstance;
5153
component.state = INITIAL_STATE;
5254
element = (fixture.nativeElement as HTMLElement).querySelector('select') as HTMLSelectElement;
55+
valueAccessor = getDebugNode(element)!.injector.get(NgrxSelectControlValueAccessor);
5356
fixture.detectChanges();
5457
});
5558

5659
it('should select the correct option initially', () => {
57-
expect(element.value).toBe(INITIAL_FORM_CONTROL_VALUE);
60+
expect(valueAccessor.value).toBe(INITIAL_FORM_CONTROL_VALUE);
5861
});
5962

6063
it('should trigger a SetValueAction with the selected value when an option is selected', done => {
@@ -66,5 +69,66 @@ describe(SelectComponent.name, () => {
6669
expect((a as SetValueAction<string>).payload.value).toBe(newValue);
6770
done();
6871
});
69-
}, 200);
72+
});
73+
});
74+
75+
const SELECT_NUMBER_OPTIONS = [1, 2];
76+
77+
@Component({
78+
// tslint:disable-next-line:component-selector
79+
selector: 'select-test',
80+
template: '<select [ngrxFormControlState]="state"><option *ngFor="let o of options" [ngValue]="o">{{o}}</option></select>',
81+
})
82+
export class NgValueSelectComponent {
83+
@Input() state: FormControlState<number>;
84+
options = SELECT_NUMBER_OPTIONS;
85+
}
86+
87+
describe(NgValueSelectComponent.name, () => {
88+
let component: NgValueSelectComponent;
89+
let fixture: ComponentFixture<NgValueSelectComponent>;
90+
let valueAccessor: NgrxSelectControlValueAccessor;
91+
let actionsSubject: ActionsSubject;
92+
let actions$: Observable<Action>;
93+
let element: HTMLSelectElement;
94+
const FORM_CONTROL_ID = 'test ID';
95+
const INITIAL_FORM_CONTROL_VALUE = SELECT_NUMBER_OPTIONS[1];
96+
const INITIAL_STATE = createFormControlState(FORM_CONTROL_ID, INITIAL_FORM_CONTROL_VALUE);
97+
98+
beforeEach(() => {
99+
actionsSubject = new BehaviorSubject<Action>({ type: '' }) as ActionsSubject;
100+
actions$ = actionsSubject as Observable<Action>; // cast required due to mismatch of lift() function signature
101+
});
102+
103+
beforeEach(async(() => {
104+
TestBed.configureTestingModule({
105+
imports: [NgrxFormsModule],
106+
declarations: [NgValueSelectComponent],
107+
providers: [{ provide: ActionsSubject, useValue: actionsSubject }],
108+
}).compileComponents();
109+
}));
110+
111+
beforeEach(() => {
112+
fixture = TestBed.createComponent(NgValueSelectComponent);
113+
component = fixture.componentInstance;
114+
component.state = INITIAL_STATE;
115+
element = (fixture.nativeElement as HTMLElement).querySelector('select') as HTMLSelectElement;
116+
valueAccessor = getDebugNode(element)!.injector.get(NgrxSelectControlValueAccessor);
117+
fixture.detectChanges();
118+
});
119+
120+
it('should select the correct option initially', () => {
121+
expect(valueAccessor.value).toBe(INITIAL_FORM_CONTROL_VALUE);
122+
});
123+
124+
it('should trigger a SetValueAction with the selected value when an option is selected', done => {
125+
const newValue = SELECT_NUMBER_OPTIONS[0];
126+
element.selectedIndex = 0;
127+
element.dispatchEvent(new Event('change'));
128+
actions$.first().subscribe(a => {
129+
expect(a.type).toBe(SetValueAction.TYPE);
130+
expect((a as SetValueAction<number>).payload.value).toBe(newValue);
131+
done();
132+
});
133+
});
70134
});

src/module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
NgrxSelectControlValueAccessor,
1111
NgrxSelectMultipleControlValueAccessor,
1212
NgrxRadioControlValueAccessor,
13+
NgrxSelectOption,
1314
} from './value-accessors';
1415

1516
const exportsAndDeclarations = [
@@ -22,6 +23,7 @@ const exportsAndDeclarations = [
2223
NgrxSelectControlValueAccessor,
2324
NgrxSelectMultipleControlValueAccessor,
2425
NgrxRadioControlValueAccessor,
26+
NgrxSelectOption,
2527
];
2628

2729
@NgModule({

src/value-accessors.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import {
22
Directive,
33
ElementRef,
44
Renderer2,
5+
Optional,
6+
Host,
57
forwardRef,
68
} from '@angular/core';
79
import {
@@ -12,6 +14,7 @@ import {
1214
SelectControlValueAccessor,
1315
SelectMultipleControlValueAccessor,
1416
RadioControlValueAccessor,
17+
NgSelectOption,
1518
} from '@angular/forms';
1619

1720
// tslint:disable:directive-selector
@@ -138,6 +141,19 @@ export class NgrxNumberValueAccessor implements ControlValueAccessor {
138141
})
139142
export class NgrxSelectControlValueAccessor extends SelectControlValueAccessor { }
140143

144+
@Directive({
145+
selector: 'option',
146+
})
147+
export class NgrxSelectOption extends NgSelectOption {
148+
constructor(
149+
element: ElementRef,
150+
renderer: Renderer2,
151+
@Optional() @Host() valueAccessor: NgrxSelectControlValueAccessor,
152+
) {
153+
super(element, renderer, valueAccessor);
154+
}
155+
}
156+
141157
@Directive({
142158
selector: 'select[multiple][ngrxFormControlState]',
143159
host: {

0 commit comments

Comments
 (0)