X Tutup
Skip to content

Commit b123159

Browse files
committed
fix(forms): do not reset the value of the input when it came from the view
1 parent b1df545 commit b123159

File tree

11 files changed

+81
-34
lines changed

11 files changed

+81
-34
lines changed

modules/angular2/src/change_detection/change_detection_util.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export var uninitialized = new Object();
99

1010
export class SimpleChange {
1111
constructor(public previousValue: any, public currentValue: any) {}
12+
13+
isFirstChange(): boolean { return this.previousValue === uninitialized; }
1214
}
1315

1416
var _simpleChangesIndex = 0;

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {setProperty} from './shared';
2121
host: {
2222
'(change)': 'onChange($event.target.checked)',
2323
'(blur)': 'onTouched()',
24-
'[checked]': 'checked',
2524
'[class.ng-untouched]': 'ngClassUntouched',
2625
'[class.ng-touched]': 'ngClassTouched',
2726
'[class.ng-pristine]': 'ngClassPristine',
@@ -31,20 +30,14 @@ import {setProperty} from './shared';
3130
}
3231
})
3332
export class CheckboxControlValueAccessor implements ControlValueAccessor {
34-
checked: boolean;
3533
onChange = (_) => {};
3634
onTouched = () => {};
3735

3836
constructor(private cd: NgControl, private renderer: Renderer, private elementRef: ElementRef) {
3937
cd.valueAccessor = this;
4038
}
4139

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-
}
40+
writeValue(value) { setProperty(this.renderer, this.elementRef, "checked", value); }
4841

4942
get ngClassUntouched(): boolean {
5043
return isPresent(this.cd.control) ? this.cd.control.untouched : false;

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import {setProperty} from './shared';
2222
'(change)': 'onChange($event.target.value)',
2323
'(input)': 'onChange($event.target.value)',
2424
'(blur)': 'onTouched()',
25-
'[value]': 'value',
2625
'[class.ng-untouched]': 'ngClassUntouched',
2726
'[class.ng-touched]': 'ngClassTouched',
2827
'[class.ng-pristine]': 'ngClassPristine',
@@ -32,8 +31,6 @@ import {setProperty} from './shared';
3231
}
3332
})
3433
export class DefaultValueAccessor implements ControlValueAccessor {
35-
value: string = null;
36-
3734
onChange = (_) => {};
3835
onTouched = () => {};
3936

@@ -44,8 +41,8 @@ export class DefaultValueAccessor implements ControlValueAccessor {
4441
writeValue(value) {
4542
// both this.value and setProperty are required at the moment
4643
// remove when a proper imperative API is provided
47-
this.value = isBlank(value) ? '' : value;
48-
setProperty(this.renderer, this.elementRef, 'value', this.value);
44+
var normalizedValue = isBlank(value) ? '' : value;
45+
setProperty(this.renderer, this.elementRef, 'value', normalizedValue);
4946
}
5047

5148
get ngClassUntouched(): boolean {

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import {CONST_EXPR} from 'angular2/src/facade/lang';
22
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
3-
import {List, StringMapWrapper, StringMap} from 'angular2/src/facade/collection';
3+
import {List, StringMap} from 'angular2/src/facade/collection';
44

55
import {Directive, LifecycleEvent, Query, QueryList} from 'angular2/angular2';
66
import {forwardRef, Ancestor, Binding, Inject} from 'angular2/di';
77

88
import {ControlContainer} from './control_container';
99
import {NgControl} from './ng_control';
1010
import {NgValidator} from './validators';
11-
import {controlPath, composeNgValidator} from './shared';
11+
import {controlPath, composeNgValidator, isPropertyUpdated} from './shared';
1212
import {Control} from '../model';
1313

1414
const controlNameBinding =
@@ -82,6 +82,7 @@ export class NgControlName extends NgControl {
8282
_parent: ControlContainer;
8383
update = new EventEmitter();
8484
model: any;
85+
viewModel: any;
8586
ngValidators: QueryList<NgValidator>;
8687
_added = false;
8788

@@ -98,14 +99,18 @@ export class NgControlName extends NgControl {
9899
this.formDirective.addControl(this);
99100
this._added = true;
100101
}
101-
if (StringMapWrapper.contains(c, "model")) {
102+
if (isPropertyUpdated(c, this.viewModel)) {
103+
this.viewModel = this.model;
102104
this.formDirective.updateModel(this, this.model);
103105
}
104106
}
105107

106108
onDestroy() { this.formDirective.removeControl(this); }
107109

108-
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.update, newValue); }
110+
viewToModelUpdate(newValue: any): void {
111+
this.viewModel = newValue;
112+
ObservableWrapper.callNext(this.update, newValue);
113+
}
109114

110115
get path(): List<string> { return controlPath(this.name, this._parent); }
111116

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {CONST_EXPR} from 'angular2/src/facade/lang';
2-
import {StringMapWrapper} from 'angular2/src/facade/collection';
32
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
43

54
import {Directive, LifecycleEvent, Query, QueryList} from 'angular2/angular2';
@@ -8,7 +7,7 @@ import {forwardRef, Ancestor, Binding} from 'angular2/di';
87
import {NgControl} from './ng_control';
98
import {Control} from '../model';
109
import {NgValidator} from './validators';
11-
import {setUpControl, composeNgValidator} from './shared';
10+
import {setUpControl, composeNgValidator, isPropertyUpdated} from './shared';
1211

1312
const formControlBinding =
1413
CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgFormControl)}));
@@ -71,6 +70,7 @@ export class NgFormControl extends NgControl {
7170
update = new EventEmitter();
7271
_added = false;
7372
model: any;
73+
viewModel: any;
7474
ngValidators: QueryList<NgValidator>;
7575

7676
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed
@@ -85,7 +85,7 @@ export class NgFormControl extends NgControl {
8585
this.form.updateValidity();
8686
this._added = true;
8787
}
88-
if (StringMapWrapper.contains(c, "model")) {
88+
if (isPropertyUpdated(c, this.viewModel)) {
8989
this.form.updateValue(this.model);
9090
}
9191
}
@@ -96,5 +96,8 @@ export class NgFormControl extends NgControl {
9696

9797
get validator(): Function { return composeNgValidator(this.ngValidators); }
9898

99-
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.update, newValue); }
99+
viewToModelUpdate(newValue: any): void {
100+
this.viewModel = newValue;
101+
ObservableWrapper.callNext(this.update, newValue);
102+
}
100103
}

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import {CONST_EXPR} from 'angular2/src/facade/lang';
22
import {EventEmitter, ObservableWrapper} from 'angular2/src/facade/async';
3-
import {StringMapWrapper} from 'angular2/src/facade/collection';
43

54
import {Directive, LifecycleEvent, QueryList, Query} from 'angular2/angular2';
65
import {forwardRef, Ancestor, Binding} from 'angular2/di';
76

87
import {NgControl} from './ng_control';
98
import {Control} from '../model';
109
import {NgValidator} from './validators';
11-
import {setUpControl, composeNgValidator} from './shared';
10+
import {setUpControl, composeNgValidator, isPropertyUpdated} from './shared';
1211

1312
const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgModel)}));
1413

@@ -41,6 +40,7 @@ export class NgModel extends NgControl {
4140
_added = false;
4241
update = new EventEmitter();
4342
model: any;
43+
viewModel: any;
4444
ngValidators: QueryList<NgValidator>;
4545

4646
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed
@@ -56,7 +56,7 @@ export class NgModel extends NgControl {
5656
this._added = true;
5757
}
5858

59-
if (StringMapWrapper.contains(c, "model")) {
59+
if (isPropertyUpdated(c, this.viewModel)) {
6060
this._control.updateValue(this.model);
6161
}
6262
}
@@ -67,5 +67,8 @@ export class NgModel extends NgControl {
6767

6868
get validator(): Function { return composeNgValidator(this.ngValidators); }
6969

70-
viewToModelUpdate(newValue: any): void { ObservableWrapper.callNext(this.update, newValue); }
70+
viewToModelUpdate(newValue: any): void {
71+
this.viewModel = newValue;
72+
ObservableWrapper.callNext(this.update, newValue);
73+
}
7174
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ export class NgSelectOption {
2929
'(change)': 'onChange($event.target.value)',
3030
'(input)': 'onChange($event.target.value)',
3131
'(blur)': 'onTouched()',
32-
'[value]': 'value',
3332
'[class.ng-untouched]': 'ngClassUntouched',
3433
'[class.ng-touched]': 'ngClassTouched',
3534
'[class.ng-pristine]': 'ngClassPristine',
@@ -39,7 +38,7 @@ export class NgSelectOption {
3938
}
4039
})
4140
export class SelectControlValueAccessor implements ControlValueAccessor {
42-
value = '';
41+
value: string;
4342
onChange = (_) => {};
4443
onTouched = () => {};
4544

@@ -51,8 +50,6 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
5150
}
5251

5352
writeValue(value) {
54-
// both this.value and setProperty are required at the moment
55-
// remove when a proper imperative API is provided
5653
this.value = value;
5754
setProperty(this.renderer, this.elementRef, "value", value);
5855
}

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import {ListWrapper, iterableToList} from 'angular2/src/facade/collection';
2-
import {isBlank, BaseException} from 'angular2/src/facade/lang';
1+
import {ListWrapper, iterableToList, StringMapWrapper} from 'angular2/src/facade/collection';
2+
import {isBlank, BaseException, looseIdentical} from 'angular2/src/facade/lang';
33

44
import {ControlContainer} from './control_container';
55
import {NgControl} from './ng_control';
@@ -26,7 +26,7 @@ export function setUpControl(c: Control, dir: NgControl) {
2626
// view -> model
2727
dir.valueAccessor.registerOnChange(newValue => {
2828
dir.viewToModelUpdate(newValue);
29-
c.updateValue(newValue);
29+
c.updateValue(newValue, {emitModelToViewChange: false});
3030
c.markAsDirty();
3131
});
3232

@@ -52,3 +52,11 @@ export function setProperty(renderer: Renderer, elementRef: ElementRef, propName
5252
propValue: any) {
5353
renderer.setElementProperty(elementRef, propName, propValue);
5454
}
55+
56+
export function isPropertyUpdated(changes: StringMap<string, any>, viewModel: any): boolean {
57+
if (!StringMapWrapper.contains(changes, "model")) return false;
58+
var change = changes["model"];
59+
60+
if (change.isFirstChange()) return true;
61+
return !looseIdentical(viewModel, change.currentValue);
62+
}

modules/angular2/src/forms/model.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,13 @@ export class Control extends AbstractControl {
151151
this._valueChanges = new EventEmitter();
152152
}
153153

154-
updateValue(value: any, {onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
154+
updateValue(value: any,
155+
{onlySelf, emitEvent, emitModelToViewChange}:
156+
{onlySelf?: boolean, emitEvent?: boolean, emitModelToViewChange?: boolean} = {}):
155157
void {
158+
emitModelToViewChange = isPresent(emitModelToViewChange) ? emitModelToViewChange : true;
156159
this._value = value;
157-
if (isPresent(this._onChange)) this._onChange(this._value);
160+
if (isPresent(this._onChange) && emitModelToViewChange) this._onChange(this._value);
158161
this.updateValueAndValidity({onlySelf: onlySelf, emitEvent: emitEvent});
159162
}
160163

modules/angular2/test/forms/integration_spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,33 @@ export function main() {
721721
});
722722
}));
723723
});
724+
725+
describe("ng-model corner cases", () => {
726+
it("should not update the view when the value initially came from the view",
727+
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
728+
var form = new Control("");
729+
730+
var t =
731+
`<div><input type="text" [ng-form-control]="form" [(ng-model)]="name"></div>`;
732+
var rootTC;
733+
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then(
734+
(root) => { rootTC = root; });
735+
tick();
736+
rootTC.componentInstance.form = form;
737+
rootTC.detectChanges();
738+
739+
var input = rootTC.query(By.css("input")).nativeElement;
740+
input.value = "aa";
741+
input.selectionStart = 1;
742+
dispatchEvent(input, "change");
743+
744+
tick();
745+
rootTC.detectChanges();
746+
747+
// selection start has not changed because we did not reset the value
748+
expect(input.selectionStart).toEqual(1);
749+
})));
750+
});
724751
});
725752
}
726753

0 commit comments

Comments
 (0)
X Tutup