fix(forms): prevent event emission on enable/disable when emitEvent is false (#12366) (#21018)

Previously, the emitEvent flag was only checked when emitting on the current control.
Thus, if  the control was part of a hierarchy, events were emitted on the parent and the childrens.
This fixes the issue by properly passing the emitEvent flag to both parent and childrens.

Fixes #12366

PR Close #21018
This commit is contained in:
Kevin Fahy 2017-12-14 16:51:05 +01:00 committed by Miško Hevery
parent 140e7c00d1
commit 0bcfae7cac
4 changed files with 73 additions and 7 deletions

View File

@ -366,7 +366,8 @@ export abstract class AbstractControl {
disable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { disable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
(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((control: AbstractControl) => { control.disable({onlySelf: true}); }); this._forEachChild(
(control: AbstractControl) => { control.disable({...opts, onlySelf: true}); });
this._updateValue(); this._updateValue();
if (opts.emitEvent !== false) { if (opts.emitEvent !== false) {
@ -374,7 +375,7 @@ export abstract class AbstractControl {
(this.statusChanges as EventEmitter<string>).emit(this.status); (this.statusChanges as EventEmitter<string>).emit(this.status);
} }
this._updateAncestors(!!opts.onlySelf); this._updateAncestors(opts);
this._onDisabledChange.forEach((changeFn) => changeFn(true)); this._onDisabledChange.forEach((changeFn) => changeFn(true));
} }
@ -387,16 +388,17 @@ export abstract class AbstractControl {
*/ */
enable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { enable(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
(this as{status: string}).status = VALID; (this as{status: string}).status = VALID;
this._forEachChild((control: AbstractControl) => { control.enable({onlySelf: true}); }); this._forEachChild(
(control: AbstractControl) => { control.enable({...opts, onlySelf: true}); });
this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent}); this.updateValueAndValidity({onlySelf: true, emitEvent: opts.emitEvent});
this._updateAncestors(!!opts.onlySelf); this._updateAncestors(opts);
this._onDisabledChange.forEach((changeFn) => changeFn(false)); this._onDisabledChange.forEach((changeFn) => changeFn(false));
} }
private _updateAncestors(onlySelf: boolean) { private _updateAncestors(opts: {onlySelf?: boolean, emitEvent?: boolean}) {
if (this._parent && !onlySelf) { if (this._parent && !opts.onlySelf) {
this._parent.updateValueAndValidity(); this._parent.updateValueAndValidity(opts);
this._parent._updatePristine(); this._parent._updatePristine();
this._parent._updateTouched(); this._parent._updateTouched();
} }

View File

@ -1053,6 +1053,28 @@ import {of } from 'rxjs/observable/of';
expect(logger).toEqual(['control', 'array', 'form']); expect(logger).toEqual(['control', 'array', 'form']);
}); });
it('should not emit value change events when emitEvent = false', () => {
c.valueChanges.subscribe(() => logger.push('control'));
a.valueChanges.subscribe(() => logger.push('array'));
form.valueChanges.subscribe(() => logger.push('form'));
a.disable({emitEvent: false});
expect(logger).toEqual([]);
a.enable({emitEvent: false});
expect(logger).toEqual([]);
});
it('should not emit status change events when emitEvent = false', () => {
c.statusChanges.subscribe(() => logger.push('control'));
a.statusChanges.subscribe(() => logger.push('array'));
form.statusChanges.subscribe(() => logger.push('form'));
a.disable({emitEvent: false});
expect(logger).toEqual([]);
a.enable({emitEvent: false});
expect(logger).toEqual([]);
});
}); });
describe('setControl()', () => { describe('setControl()', () => {

View File

@ -1139,6 +1139,26 @@ import {FormArray} from '@angular/forms/src/model';
expect(fn).toThrowError(`Expected validator to return Promise or Observable.`); expect(fn).toThrowError(`Expected validator to return Promise or Observable.`);
}); });
it('should not emit value change events when emitEvent = false', () => {
c.valueChanges.subscribe(() => logger.push('control'));
g.valueChanges.subscribe(() => logger.push('group'));
c.disable({emitEvent: false});
expect(logger).toEqual([]);
c.enable({emitEvent: false});
expect(logger).toEqual([]);
});
it('should not emit status change events when emitEvent = false', () => {
c.statusChanges.subscribe(() => logger.push('control'));
g.statusChanges.subscribe(() => logger.push('form'));
c.disable({emitEvent: false});
expect(logger).toEqual([]);
c.enable({emitEvent: false});
expect(logger).toEqual([]);
});
}); });
}); });
}); });

View File

@ -1045,6 +1045,28 @@ import {of } from 'rxjs/observable/of';
expect(logger).toEqual(['control', 'group', 'form']); expect(logger).toEqual(['control', 'group', 'form']);
}); });
it('should not emit value change events when emitEvent = false', () => {
c.valueChanges.subscribe(() => logger.push('control'));
g.valueChanges.subscribe(() => logger.push('group'));
form.valueChanges.subscribe(() => logger.push('form'));
g.disable({emitEvent: false});
expect(logger).toEqual([]);
g.enable({emitEvent: false});
expect(logger).toEqual([]);
});
it('should not emit status change events when emitEvent = false', () => {
c.statusChanges.subscribe(() => logger.push('control'));
g.statusChanges.subscribe(() => logger.push('group'));
form.statusChanges.subscribe(() => logger.push('form'));
g.disable({emitEvent: false});
expect(logger).toEqual([]);
g.enable({emitEvent: false});
expect(logger).toEqual([]);
});
}); });
}); });