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:
parent
2da82db3bc
commit
ef6728207b
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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');
|
||||||
|
|
Loading…
Reference in New Issue