diff --git a/modules/@angular/forms/src/directives/shared.ts b/modules/@angular/forms/src/directives/shared.ts index 16a8c2a323..bb8b7aa1b0 100644 --- a/modules/@angular/forms/src/directives/shared.ts +++ b/modules/@angular/forms/src/directives/shared.ts @@ -45,8 +45,8 @@ export function setUpControl(control: FormControl, dir: NgControl): void { // view -> model dir.valueAccessor.registerOnChange((newValue: any) => { dir.viewToModelUpdate(newValue); - control.updateValue(newValue, {emitModelToViewChange: false}); control.markAsDirty(); + control.updateValue(newValue, {emitModelToViewChange: false}); }); control.registerOnChange((newValue: any, emitModelEvent: boolean) => { diff --git a/modules/@angular/forms/src/model.ts b/modules/@angular/forms/src/model.ts index 1edd2c5295..821c72eb22 100644 --- a/modules/@angular/forms/src/model.ts +++ b/modules/@angular/forms/src/model.ts @@ -409,9 +409,9 @@ export class FormControl extends AbstractControl { } reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void { - this.updateValue(value, {onlySelf: onlySelf}); this.markAsPristine({onlySelf: onlySelf}); this.markAsUntouched({onlySelf: onlySelf}); + this.updateValue(value, {onlySelf: onlySelf}); } /** diff --git a/modules/@angular/forms/test/reactive_integration_spec.ts b/modules/@angular/forms/test/reactive_integration_spec.ts index 3d9276c9dd..0316084a40 100644 --- a/modules/@angular/forms/test/reactive_integration_spec.ts +++ b/modules/@angular/forms/test/reactive_integration_spec.ts @@ -236,6 +236,33 @@ export function main() { }); })); + it('should mark controls as dirty before emitting a value change event', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const login = new FormControl('oldValue'); + const form = new FormGroup({'login': login}); + + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + login.valueChanges.subscribe(() => { + expect(login.dirty).toBe(true); + async.done(); + }); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + loginEl.value = 'newValue'; + + dispatchEvent(loginEl, 'input'); + }); + })); + it('should clear value in UI when form resets programmatically', inject( [TestComponentBuilder, AsyncTestCompleter], @@ -289,6 +316,37 @@ export function main() { }); })); + it('should mark control as pristine before emitting a value change event when resetting ', + inject( + [TestComponentBuilder, AsyncTestCompleter], + (tcb: TestComponentBuilder, async: AsyncTestCompleter) => { + const login = new FormControl('oldValue'); + const form = new FormGroup({'login': login}); + + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + fixture.debugElement.componentInstance.form = form; + fixture.detectChanges(); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + loginEl.value = 'newValue'; + + dispatchEvent(loginEl, 'input'); + + expect(login.pristine).toBe(false); + + login.valueChanges.subscribe(() => { + expect(login.pristine).toBe(true); + async.done(); + }); + + form.reset(); + }); + })); + it('should support form arrays', fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); diff --git a/modules/@angular/forms/test/template_integration_spec.ts b/modules/@angular/forms/test/template_integration_spec.ts index 75eec3e60d..0f712099e3 100644 --- a/modules/@angular/forms/test/template_integration_spec.ts +++ b/modules/@angular/forms/test/template_integration_spec.ts @@ -369,6 +369,58 @@ export function main() { }); })); + it('should mark controls as dirty before emitting a value change event', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + fixture.detectChanges(); + + const form = fixture.debugElement.children[0].injector.get(NgForm).form; + fixture.detectChanges(); + tick(); + + form.find('login').valueChanges.subscribe( + () => { expect(form.find('login').dirty).toBe(true); }); + + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + loginEl.value = 'newValue'; + + dispatchEvent(loginEl, 'input'); + }); + }))); + + it('should mark control as pristine before emitting a value change event when resetting ', + fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { + + const t = `
+ +
`; + + tcb.overrideTemplate(MyComp8, t).createAsync(MyComp8).then((fixture) => { + fixture.detectChanges(); + + const form = fixture.debugElement.children[0].injector.get(NgForm).form; + const formEl = fixture.debugElement.query(By.css('form')).nativeElement; + const loginEl = fixture.debugElement.query(By.css('input')).nativeElement; + fixture.detectChanges(); + tick(); + + loginEl.value = 'newValue'; + dispatchEvent(loginEl, 'input'); + + expect(form.find('login').pristine).toBe(false); + + form.find('login').valueChanges.subscribe( + () => { expect(form.find('login').pristine).toBe(true); }); + + dispatchEvent(formEl, 'reset'); + }); + }))); + describe('radio value accessor', () => { it('should support ', fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {