Skip to content

Commit f1541e6

Browse files
committed
fix(forms): fixed the handling of the select element
1 parent 9bad70b commit f1541e6

File tree

6 files changed

+90
-15
lines changed

6 files changed

+90
-15
lines changed

modules/angular2/src/forms/directives.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import {NgFormModel} from './directives/ng_form_model';
77
import {NgForm} from './directives/ng_form';
88
import {DefaultValueAccessor} from './directives/default_value_accessor';
99
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
10-
import {SelectControlValueAccessor} from './directives/select_control_value_accessor';
10+
import {
11+
SelectControlValueAccessor,
12+
NgSelectOption
13+
} from './directives/select_control_value_accessor';
1114
import {NgRequiredValidator} from './directives/validators';
1215

1316
export {NgControlName} from './directives/ng_control_name';
@@ -40,6 +43,7 @@ export const formDirectives: List<Type> = CONST_EXPR([
4043
NgFormModel,
4144
NgForm,
4245

46+
NgSelectOption,
4347
DefaultValueAccessor,
4448
CheckboxControlValueAccessor,
4549
SelectControlValueAccessor,

modules/angular2/src/forms/directives/checkbox_value_accessor.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {Directive} from 'angular2/angular2';
1+
import {Directive, Renderer, ElementRef} from 'angular2/angular2';
22
import {NgControl} from './ng_control';
33
import {ControlValueAccessor} from './control_value_accessor';
4+
import {setProperty} from './shared';
45

56
/**
67
* The accessor for writing a value and listening to changes on a checkbox input element.
@@ -32,13 +33,18 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
3233
onChange: Function;
3334
onTouched: Function;
3435

35-
constructor(private cd: NgControl) {
36+
constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef) {
3637
this.onChange = (_) => {};
3738
this.onTouched = (_) => {};
3839
cd.valueAccessor = this;
3940
}
4041

41-
writeValue(value) { this.checked = value; }
42+
writeValue(value) {
43+
// both this.checked and setProperty are required at the moment
44+
// remove when a proper imperative API is provided
45+
this.checked = value;
46+
setProperty(this.renderer, this.elementRef, "checked", value);
47+
}
4248

4349
registerOnChange(fn): void { this.onChange = fn; }
4450
registerOnTouched(fn): void { this.onTouched = fn; }

modules/angular2/src/forms/directives/default_value_accessor.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import {Directive} from 'angular2/angular2';
1+
import {Directive, Renderer, ElementRef} from 'angular2/angular2';
22
import {NgControl} from './ng_control';
33
import {ControlValueAccessor} from './control_value_accessor';
44
import {isBlank} from 'angular2/src/facade/lang';
5+
import {setProperty} from './shared';
56

67
/**
78
* The default accessor for writing a value and listening to changes that is used by the
@@ -35,13 +36,18 @@ export class DefaultValueAccessor implements ControlValueAccessor {
3536
onChange: Function;
3637
onTouched: Function;
3738

38-
constructor(private cd: NgControl) {
39+
constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef) {
3940
this.onChange = (_) => {};
4041
this.onTouched = (_) => {};
4142
cd.valueAccessor = this;
4243
}
4344

44-
writeValue(value) { this.value = isBlank(value) ? "" : value; }
45+
writeValue(value) {
46+
// both this.value and setProperty are required at the moment
47+
// remove when a proper imperative API is provided
48+
this.value = isBlank(value) ? '' : value;
49+
setProperty(this.renderer, this.elementRef, 'value', this.value);
50+
}
4551

4652
registerOnChange(fn): void { this.onChange = fn; }
4753

modules/angular2/src/forms/directives/select_control_value_accessor.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
1-
import {Directive} from 'angular2/angular2';
1+
import {Directive, Query, QueryList, Renderer, ElementRef} from 'angular2/angular2';
22
import {NgControl} from './ng_control';
33
import {ControlValueAccessor} from './control_value_accessor';
4+
import {setProperty} from './shared';
5+
6+
/**
7+
* Marks <option> as dynamic, so Angular can be notified when options change.
8+
*
9+
* #Example:
10+
* ```
11+
* <select ng-control="city">
12+
* <option *ng-for="#c of cities" [value]="c"></option>
13+
* </select>
14+
* ``
15+
* @exportedAs angular2/forms
16+
*/
17+
@Directive({selector: 'option'})
18+
export class NgSelectOption {
19+
}
420

521
/**
622
* The accessor for writing a value and listening to changes on a select element.
@@ -23,19 +39,30 @@ import {ControlValueAccessor} from './control_value_accessor';
2339
}
2440
})
2541
export class SelectControlValueAccessor implements ControlValueAccessor {
26-
value = null;
42+
value = '';
2743
onChange: Function;
2844
onTouched: Function;
2945

30-
constructor(private cd: NgControl) {
46+
constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef,
47+
@Query(NgSelectOption, {descendants: true}) query: QueryList<NgSelectOption>) {
3148
this.onChange = (_) => {};
3249
this.onTouched = (_) => {};
33-
this.value = '';
3450
cd.valueAccessor = this;
51+
52+
this._updateValueWhenListOfOptionsChanges(query);
3553
}
3654

37-
writeValue(value) { this.value = value; }
55+
writeValue(value) {
56+
// both this.value and setProperty are required at the moment
57+
// remove when a proper imperative API is provided
58+
this.value = value;
59+
setProperty(this.renderer, this.elementRef, "value", value);
60+
}
3861

3962
registerOnChange(fn): void { this.onChange = fn; }
4063
registerOnTouched(fn): void { this.onTouched = fn; }
64+
65+
private _updateValueWhenListOfOptionsChanges(query: QueryList<NgSelectOption>) {
66+
query.onChange(() => this.writeValue(this.value));
67+
}
4168
}

modules/angular2/src/forms/directives/shared.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {ControlContainer} from './control_container';
55
import {NgControl} from './ng_control';
66
import {Control} from '../model';
77
import {Validators} from '../validators';
8+
import {Renderer, ElementRef} from 'angular2/angular2';
9+
810

911
export function controlPath(name, parent: ControlContainer) {
1012
var p = ListWrapper.clone(parent.path);
@@ -36,4 +38,10 @@ export function setUpControl(c: Control, dir: NgControl) {
3638
function _throwError(dir: NgControl, message: string): void {
3739
var path = ListWrapper.join(dir.path, " -> ");
3840
throw new BaseException(`${message} '${path}'`);
41+
}
42+
43+
export function setProperty(renderer: Renderer, elementRef: ElementRef, propName: string,
44+
propValue: any) {
45+
renderer.setElementProperty(elementRef.parentView.render, elementRef.boundElementIndex, propName,
46+
propValue);
3947
}

modules/angular2/test/forms/integration_spec.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919

2020
import {DOM} from 'angular2/src/dom/dom_adapter';
2121
import {TestBed} from 'angular2/src/test_lib/test_bed';
22-
import {NgIf} from 'angular2/directives';
22+
import {NgIf, NgFor} from 'angular2/directives';
2323

2424
import {
2525
Control,
@@ -300,6 +300,28 @@ export function main() {
300300
});
301301
}));
302302

303+
it("should support <select> with a dynamic list of options",
304+
inject([TestBed], fakeAsync((tb: TestBed) => {
305+
var ctx = MyComp.create(
306+
{form: new ControlGroup({"city": new Control("NYC")}), data: ['SF', 'NYC']});
307+
308+
var t = `<div [ng-form-model]="form">
309+
<select ng-control="city">
310+
<option *ng-for="#c of data" [value]="c"></option>
311+
</select>
312+
</div>`;
313+
314+
tb.createView(MyComp, {context: ctx, html: t})
315+
.then((view) => {
316+
view.detectChanges();
317+
tick();
318+
319+
var select = view.querySelector('select');
320+
321+
expect(select.value).toEqual('NYC');
322+
});
323+
})));
324+
303325
it("should support custom value accessors",
304326
inject([TestBed, AsyncTestCompleter], (tb: TestBed, async) => {
305327
var ctx = MyComp.create({form: new ControlGroup({"name": new Control("aa")})});
@@ -752,15 +774,17 @@ class WrappedValue implements ControlValueAccessor {
752774
}
753775

754776
@Component({selector: "my-comp"})
755-
@View({directives: [formDirectives, WrappedValue, NgIf]})
777+
@View({directives: [formDirectives, WrappedValue, NgIf, NgFor]})
756778
class MyComp {
757779
form: any;
758780
name: string;
781+
data: any;
759782

760-
static create({form, name}: {form?: any, name?: any}) {
783+
static create({form, name, data}: {form?: any, name?: any, data?: any}) {
761784
var mc = new MyComp();
762785
mc.form = form;
763786
mc.name = name;
787+
mc.data = data;
764788
return mc;
765789
}
766790
}

0 commit comments

Comments
 (0)