fix(core/forms): input[type=text] .valueChanges fires unexpectedly

Closes #4768, #5284

Closes #5401
This commit is contained in:
erictsangx 2015-11-20 17:19:04 +08:00
parent 688469c221
commit 680f7e0299
2 changed files with 56 additions and 36 deletions

View File

@ -20,11 +20,7 @@ const DEFAULT_VALUE_ACCESSOR = CONST_EXPR(new Provider(
// TODO: vsavkin replace the above selector with the one below it once // TODO: vsavkin replace the above selector with the one below it once
// https://github.com/angular/angular/issues/3011 is implemented // https://github.com/angular/angular/issues/3011 is implemented
// selector: '[ng-control],[ng-model],[ng-form-control]', // selector: '[ng-control],[ng-model],[ng-form-control]',
host: { host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
'(change)': 'onChange($event.target.value)',
'(input)': 'onChange($event.target.value)',
'(blur)': 'onTouched()'
},
bindings: [DEFAULT_VALUE_ACCESSOR] bindings: [DEFAULT_VALUE_ACCESSOR]
}) })
export class DefaultValueAccessor implements ControlValueAccessor { export class DefaultValueAccessor implements ControlValueAccessor {
@ -40,4 +36,4 @@ export class DefaultValueAccessor implements ControlValueAccessor {
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
registerOnTouched(fn: () => void): void { this.onTouched = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; }
} }

View File

@ -74,13 +74,37 @@ export function main() {
var input = fixture.debugElement.query(By.css("input")); var input = fixture.debugElement.query(By.css("input"));
input.nativeElement.value = "updatedValue"; input.nativeElement.value = "updatedValue";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(form.value).toEqual({"login": "updatedValue"}); expect(form.value).toEqual({"login": "updatedValue"});
async.done(); async.done();
}); });
})); }));
it("should ignore the change event for <input type=text>",
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var form = new ControlGroup({"login": new Control("oldValue")});
var t = `<div [ng-form-model]="form">
<input type="text" ng-control="login">
</div>`;
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((fixture) => {
fixture.debugElement.componentInstance.form = form;
fixture.detectChanges();
var input = fixture.debugElement.query(By.css("input"));
input.nativeElement.value = "updatedValue";
ObservableWrapper.subscribe(form.valueChanges,
(value) => { throw 'Should not happen'; });
dispatchEvent(input.nativeElement, "change");
async.done();
});
}));
it("should emit ng-submit event on submit", it("should emit ng-submit event on submit",
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => { inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
var t = `<div> var t = `<div>
@ -120,7 +144,7 @@ export function main() {
expect(input.nativeElement.value).toEqual("loginValue"); expect(input.nativeElement.value).toEqual("loginValue");
input.nativeElement.value = "updatedValue"; input.nativeElement.value = "updatedValue";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(control.value).toEqual("updatedValue"); expect(control.value).toEqual("updatedValue");
async.done(); async.done();
@ -298,7 +322,7 @@ export function main() {
expect(input.nativeElement.value).toEqual("10"); expect(input.nativeElement.value).toEqual("10");
input.nativeElement.value = "20"; input.nativeElement.value = "20";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(fixture.debugElement.componentInstance.form.value).toEqual({"num": 20}); expect(fixture.debugElement.componentInstance.form.value).toEqual({"num": 20});
async.done(); async.done();
@ -370,7 +394,7 @@ export function main() {
expect(input.nativeElement.value).toEqual("!aa!"); expect(input.nativeElement.value).toEqual("!aa!");
input.nativeElement.value = "!bb!"; input.nativeElement.value = "!bb!";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(fixture.debugElement.componentInstance.form.value).toEqual({"name": "bb"}); expect(fixture.debugElement.componentInstance.form.value).toEqual({"name": "bb"});
async.done(); async.done();
@ -391,7 +415,7 @@ export function main() {
expect(input.componentInstance.value).toEqual("!aa!"); expect(input.componentInstance.value).toEqual("!aa!");
input.componentInstance.value = "!bb!"; input.componentInstance.value = "!bb!";
ObservableWrapper.subscribe(input.componentInstance.onChange, (value) => { ObservableWrapper.subscribe(input.componentInstance.onInput, (value) => {
expect(fixture.debugElement.componentInstance.form.value).toEqual({"name": "bb"}); expect(fixture.debugElement.componentInstance.form.value).toEqual({"name": "bb"});
async.done(); async.done();
}); });
@ -424,9 +448,9 @@ export function main() {
required.nativeElement.value = ""; required.nativeElement.value = "";
minLength.nativeElement.value = "1"; minLength.nativeElement.value = "1";
maxLength.nativeElement.value = "1234"; maxLength.nativeElement.value = "1234";
dispatchEvent(required.nativeElement, "change"); dispatchEvent(required.nativeElement, "input");
dispatchEvent(minLength.nativeElement, "change"); dispatchEvent(minLength.nativeElement, "input");
dispatchEvent(maxLength.nativeElement, "change"); dispatchEvent(maxLength.nativeElement, "input");
expect(form.hasError("required", ["login"])).toEqual(true); expect(form.hasError("required", ["login"])).toEqual(true);
expect(form.hasError("minlength", ["min"])).toEqual(true); expect(form.hasError("minlength", ["min"])).toEqual(true);
@ -437,9 +461,9 @@ export function main() {
required.nativeElement.value = "1"; required.nativeElement.value = "1";
minLength.nativeElement.value = "123"; minLength.nativeElement.value = "123";
maxLength.nativeElement.value = "123"; maxLength.nativeElement.value = "123";
dispatchEvent(required.nativeElement, "change"); dispatchEvent(required.nativeElement, "input");
dispatchEvent(minLength.nativeElement, "change"); dispatchEvent(minLength.nativeElement, "input");
dispatchEvent(maxLength.nativeElement, "change"); dispatchEvent(maxLength.nativeElement, "input");
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
@ -470,7 +494,7 @@ export function main() {
var input = rootTC.debugElement.query(By.css("input")); var input = rootTC.debugElement.query(By.css("input"));
input.nativeElement.value = "expected"; input.nativeElement.value = "expected";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
tick(100); tick(100);
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
@ -492,7 +516,7 @@ export function main() {
var input = fixture.debugElement.query(By.css("input")); var input = fixture.debugElement.query(By.css("input"));
input.nativeElement.value = ""; input.nativeElement.value = "";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(form.valid).toEqual(false); expect(form.valid).toEqual(false);
async.done(); async.done();
@ -521,7 +545,7 @@ export function main() {
var input = fixture.debugElement.query(By.css("input")); var input = fixture.debugElement.query(By.css("input"));
input.nativeElement.value = "wrong value"; input.nativeElement.value = "wrong value";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(form.pending).toEqual(true); expect(form.pending).toEqual(true);
tick(); tick();
@ -529,7 +553,7 @@ export function main() {
expect(form.hasError("uniqLogin", ["login"])).toEqual(true); expect(form.hasError("uniqLogin", ["login"])).toEqual(true);
input.nativeElement.value = "expected"; input.nativeElement.value = "expected";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
tick(); tick();
expect(form.valid).toEqual(true); expect(form.valid).toEqual(true);
@ -575,7 +599,7 @@ export function main() {
var input = fixture.debugElement.query(By.css("input")); var input = fixture.debugElement.query(By.css("input"));
input.nativeElement.value = "updatedValue"; input.nativeElement.value = "updatedValue";
dispatchEvent(input.nativeElement, "change"); dispatchEvent(input.nativeElement, "input");
expect(form.value).toEqual({"nested": {"login": "updatedValue"}}); expect(form.value).toEqual({"nested": {"login": "updatedValue"}});
async.done(); async.done();
@ -604,7 +628,7 @@ export function main() {
expect(input.value).toEqual("oldValue"); expect(input.value).toEqual("oldValue");
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
tick(); tick();
expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue");
@ -629,7 +653,7 @@ export function main() {
expect(input.value).toEqual("oldValue"); expect(input.value).toEqual("oldValue");
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
tick(); tick();
expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue");
@ -763,7 +787,7 @@ export function main() {
expect(input.value).toEqual("oldValue"); expect(input.value).toEqual("oldValue");
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
tick(); tick();
expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue");
@ -785,7 +809,7 @@ export function main() {
expect(input.value).toEqual("oldValue"); expect(input.value).toEqual("oldValue");
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
tick(); tick();
expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue"); expect(fixture.debugElement.componentInstance.name).toEqual("updatedValue");
@ -813,7 +837,7 @@ export function main() {
expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]); expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]);
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
fixture.detectChanges(); fixture.detectChanges();
expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]); expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]);
@ -840,7 +864,7 @@ export function main() {
expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]); expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]);
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
fixture.detectChanges(); fixture.detectChanges();
expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]); expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]);
@ -865,7 +889,7 @@ export function main() {
expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]); expect(sortedClassList(input)).toEqual(["ng-invalid", "ng-pristine", "ng-touched"]);
input.value = "updatedValue"; input.value = "updatedValue";
dispatchEvent(input, "change"); dispatchEvent(input, "input");
fixture.detectChanges(); fixture.detectChanges();
expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]); expect(sortedClassList(input)).toEqual(["ng-dirty", "ng-touched", "ng-valid"]);
@ -898,7 +922,7 @@ export function main() {
var input = fixture.debugElement.query(By.css("input")).nativeElement; var input = fixture.debugElement.query(By.css("input")).nativeElement;
input.value = "aa"; input.value = "aa";
input.selectionStart = 1; input.selectionStart = 1;
dispatchEvent(input, "change"); dispatchEvent(input, "input");
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
@ -921,7 +945,7 @@ export function main() {
var input = fixture.debugElement.query(By.css("input")).nativeElement; var input = fixture.debugElement.query(By.css("input")).nativeElement;
input.value = "aa"; input.value = "aa";
input.selectionStart = 1; input.selectionStart = 1;
dispatchEvent(input, "change"); dispatchEvent(input, "input");
tick(); tick();
fixture.detectChanges(); fixture.detectChanges();
@ -958,7 +982,7 @@ export function main() {
@Directive({ @Directive({
selector: '[wrapped-value]', selector: '[wrapped-value]',
host: {'(change)': 'handleOnChange($event.target.value)', '[value]': 'value'} host: {'(input)': 'handleOnInput($event.target.value)', '[value]': 'value'}
}) })
class WrappedValue implements ControlValueAccessor { class WrappedValue implements ControlValueAccessor {
value; value;
@ -971,24 +995,24 @@ class WrappedValue implements ControlValueAccessor {
registerOnChange(fn) { this.onChange = fn; } registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn) {} registerOnTouched(fn) {}
handleOnChange(value) { this.onChange(value.substring(1, value.length - 1)); } handleOnInput(value) { this.onChange(value.substring(1, value.length - 1)); }
} }
@Component({selector: "my-input", template: ''}) @Component({selector: "my-input", template: ''})
class MyInput implements ControlValueAccessor { class MyInput implements ControlValueAccessor {
@Output('change') onChange: EventEmitter<any> = new EventEmitter(); @Output('input') onInput: EventEmitter<any> = new EventEmitter();
value: string; value: string;
constructor(cd: NgControl) { cd.valueAccessor = this; } constructor(cd: NgControl) { cd.valueAccessor = this; }
writeValue(value) { this.value = `!${value}!`; } writeValue(value) { this.value = `!${value}!`; }
registerOnChange(fn) { ObservableWrapper.subscribe(this.onChange, fn); } registerOnChange(fn) { ObservableWrapper.subscribe(this.onInput, fn); }
registerOnTouched(fn) {} registerOnTouched(fn) {}
dispatchChangeEvent() { dispatchChangeEvent() {
ObservableWrapper.callEmit(this.onChange, this.value.substring(1, this.value.length - 1)); ObservableWrapper.callEmit(this.onInput, this.value.substring(1, this.value.length - 1));
} }
} }