fix(forms): don't override form group's dirty state when disabling controls (#24591)

Update packages/forms/src/model.ts

Co-Authored-By: martinsik <martin.sikora.ahoj@gmail.com>

PR Close #24591
This commit is contained in:
Martin Sikora 2018-06-20 17:16:42 +02:00 committed by Jason Aden
parent 2da82db3bc
commit ef6728207b
2 changed files with 72 additions and 4 deletions

View File

@ -495,6 +495,10 @@ export abstract class AbstractControl {
* When false, no events are emitted. * When false, no events are emitted.
*/ */
disable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { disable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
// If parent has been marked artificially dirty we don't want to re-calculate the
// parent's dirtiness based on the children.
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
(this as{status: string}).status = DISABLED; (this as{status: string}).status = DISABLED;
(this as{errors: ValidationErrors | null}).errors = null; (this as{errors: ValidationErrors | null}).errors = null;
this._forEachChild( this._forEachChild(
@ -506,7 +510,7 @@ export abstract class AbstractControl {
(this.statusChanges as EventEmitter<string>).emit(this.status); (this.statusChanges as EventEmitter<string>).emit(this.status);
} }
this._updateAncestors(opts); this._updateAncestors({...opts, skipPristineCheck});
this._onDisabledChange.forEach((changeFn) => changeFn(true)); this._onDisabledChange.forEach((changeFn) => changeFn(true));
} }
@ -529,19 +533,26 @@ export abstract class AbstractControl {
* When false, no events are emitted. * When false, no events are emitted.
*/ */
enable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { enable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
// If parent has been marked artificially dirty we don't want to re-calculate the
// parent's dirtiness based on the children.
const skipPristineCheck = this._parentMarkedDirty(opts.onlySelf);
(this as{status: string}).status = VALID; (this as{status: string}).status = VALID;
this._forEachChild( this._forEachChild(
(control: AbstractControl) => { control.enable({...opts, onlySelf: true}); }); (control: AbstractControl) => { control.enable({...opts, onlySelf: true}); });
this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent}); this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent});
this._updateAncestors(opts); this._updateAncestors({...opts, skipPristineCheck});
this._onDisabledChange.forEach((changeFn) => changeFn(false)); this._onDisabledChange.forEach((changeFn) => changeFn(false));
} }
private _updateAncestors(opts: {onlySelf?: boolean, emitEvent?: boolean}) { private _updateAncestors(
opts: {onlySelf?: boolean, emitEvent?: boolean, skipPristineCheck?: boolean}) {
if (this._parent && !opts.onlySelf) { if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity(opts); this._parent.updateValueAndValidity(opts);
this._parent._updatePristine(); if (!opts.skipPristineCheck) {
this._parent._updatePristine();
}
this._parent._updateTouched(); this._parent._updateTouched();
} }
} }
@ -852,6 +863,16 @@ export abstract class AbstractControl {
this._updateOn = (opts as AbstractControlOptions).updateOn !; this._updateOn = (opts as AbstractControlOptions).updateOn !;
} }
} }
/**
* Check to see if parent has been marked artificially dirty.
*
* @internal
*/
private _parentMarkedDirty(onlySelf?: boolean): boolean {
const parentDirty = this._parent && this._parent.dirty;
return !onlySelf && parentDirty && !this._parent._anyControlsDirty();
}
} }
/** /**

View File

@ -1000,6 +1000,37 @@ import {FormArray} from '@angular/forms/src/model';
expect(a.value).toEqual(['one']); expect(a.value).toEqual(['one']);
}); });
it('should ignore disabled array controls when determining dirtiness', () => {
const c = new FormControl('one');
const c2 = new FormControl('two');
const a = new FormArray([c, c2]);
c.markAsDirty();
expect(a.dirty).toBe(true);
c.disable();
expect(c.dirty).toBe(true);
expect(a.dirty).toBe(false);
c.enable();
expect(a.dirty).toBe(true);
});
it('should not make a dirty array not dirty when disabling controls', () => {
const c = new FormControl('one');
const c2 = new FormControl('two');
const a = new FormArray([c, c2]);
a.markAsDirty();
expect(a.dirty).toBe(true);
expect(c.dirty).toBe(false);
c.disable();
expect(a.dirty).toBe(true);
c.enable();
expect(a.dirty).toBe(true);
});
it('should ignore disabled controls in validation', () => { it('should ignore disabled controls in validation', () => {
const c = new FormControl(null, Validators.required); const c = new FormControl(null, Validators.required);
const c2 = new FormControl(null); const c2 = new FormControl(null);
@ -1054,6 +1085,22 @@ import {FormArray} from '@angular/forms/src/model';
expect(g.dirty).toBe(true); expect(g.dirty).toBe(true);
}); });
it('should not make a dirty group not dirty when disabling controls', () => {
const c = new FormControl('one');
const c2 = new FormControl('two');
const g = new FormGroup({one: c, two: c2});
g.markAsDirty();
expect(g.dirty).toBe(true);
expect(c.dirty).toBe(false);
c.disable();
expect(g.dirty).toBe(true);
c.enable();
expect(g.dirty).toBe(true);
});
it('should ignore disabled controls when determining touched state', () => { it('should ignore disabled controls when determining touched state', () => {
const c = new FormControl('one'); const c = new FormControl('one');
const c2 = new FormControl('two'); const c2 = new FormControl('two');