fix(core/forms): input[type=text] .valueChanges fires unexpectedly
Closes #4768, #5284 Closes #5401
This commit is contained in:
parent
688469c221
commit
680f7e0299
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue