fix(forms): update dirty before emitting value change (#10362)

Closes #5328
This commit is contained in:
Kara 2016-07-28 14:25:33 -07:00 committed by GitHub
parent a32c4ad2f0
commit 7c76a75452
4 changed files with 112 additions and 2 deletions

View File

@ -45,8 +45,8 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
// view -> model // view -> model
dir.valueAccessor.registerOnChange((newValue: any) => { dir.valueAccessor.registerOnChange((newValue: any) => {
dir.viewToModelUpdate(newValue); dir.viewToModelUpdate(newValue);
control.updateValue(newValue, {emitModelToViewChange: false});
control.markAsDirty(); control.markAsDirty();
control.updateValue(newValue, {emitModelToViewChange: false});
}); });
control.registerOnChange((newValue: any, emitModelEvent: boolean) => { control.registerOnChange((newValue: any, emitModelEvent: boolean) => {

View File

@ -409,9 +409,9 @@ export class FormControl extends AbstractControl {
} }
reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void { reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
this.updateValue(value, {onlySelf: onlySelf});
this.markAsPristine({onlySelf: onlySelf}); this.markAsPristine({onlySelf: onlySelf});
this.markAsUntouched({onlySelf: onlySelf}); this.markAsUntouched({onlySelf: onlySelf});
this.updateValue(value, {onlySelf: onlySelf});
} }
/** /**

View File

@ -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 = `<div [formGroup]="form">
<input type="text" formControlName="login">
</div>`;
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', it('should clear value in UI when form resets programmatically',
inject( inject(
[TestComponentBuilder, AsyncTestCompleter], [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 = `<div [formGroup]="form">
<input type="text" formControlName="login">
</div>`;
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', it('should support form arrays',
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]); const cityArray = new FormArray([new FormControl('SF'), new FormControl('NY')]);

View File

@ -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 = `<form>
<input type="text" name="login" ngModel>
</form>`;
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 = `<form>
<input type="text" name="login" ngModel>
</form>`;
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', () => { describe('radio value accessor', () => {
it('should support <type=radio>', it('should support <type=radio>',
fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { fakeAsync(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {