With Angular (4.x) I use ReactiveForms and I've subscribed to valueChanges on my FormControl ("input") like so:
export class App {
version:string;
formControl = new FormControl('default', []);
form = this.fb.group({
input: this.formControl,
input2: ('',[])
});
constructor(private fb: FormBuilder) {
this.version = `Angular v${VERSION.full}`
}
ngOnInit() {
this.formControl.valueChanges.subscribe(value => doSomething(value));
}
So now I can react on changes on the value of my FormControl, but I of course fill the values of the form from somewhere to start with, so I use form.patchValue(data)
to do so.
Since this is not a userchange, I don't want to react on it, so add the flag emitEvent: false
, like: this.form.patchValue(data, {emitEvent: false})
.
Which works as expected.
Now I have some logic that when the form is loading, I set the whole form to disabled, this.form.disable({ emitEvent: false })
, and when finished loading it sets the whole form to enabled again: this.form.disable({ emitEvent: false })
But I also have logic that depending on different flags sets the FormControl to enabled/disabled: this.formControl.enable( {emitEvent: false});
The problem I'm now see is that when the Form
, changes status, it triggers the FormControl.valueChanges
, even though I provide the emitEvent: false
flag.
Is this the expected behavior, or a bug? I expected no event to be triggered at all when providing the flag?
I've made a plunk where this can be tested here: https://plnkr.co/edit/RgyDItYtEfzlLVB6P5f3?p=preview
both disable()
and enable()
functions (code source):
/**
* Disables the control. This means the control will be exempt from validation checks and
* excluded from the aggregate value of any parent. Its status is `DISABLED`.
*
* If the control has children, all children will be disabled to maintain the model.
* @param {?=} opts
* @return {?}
*/
AbstractControl.prototype.disable = function (opts) {
if (opts === void 0) { opts = {}; }
this._status = DISABLED;
this._errors = null;
this._forEachChild(function (control) { control.disable({ onlySelf: true }); });
this._updateValue();
if (opts.emitEvent !== false) {
this._valueChanges.emit(this._value);
this._statusChanges.emit(this._status);
}
this._updateAncestors(!!opts.onlySelf);
this._onDisabledChange.forEach(function (changeFn) { return changeFn(true); });
};
/**
* Enables the control. This means the control will be included in validation checks and
* the aggregate value of its parent. Its status is re-calculated based on its value and
* its validators.
*
* If the control has children, all children will be enabled.
* @param {?=} opts
* @return {?}
*/
AbstractControl.prototype.enable = function (opts) {
if (opts === void 0) { opts = {}; }
this._status = VALID;
this._forEachChild(function (control) { control.enable({ onlySelf: true }); });
this.updateValueAndValidity({ onlySelf: true, emitEvent: opts.emitEvent });
this._updateAncestors(!!opts.onlySelf);
this._onDisabledChange.forEach(function (changeFn) { return changeFn(false); });
};
have call to:
this._updateAncestors(!!opts.onlySelf);
which calls its parent's updateValueAndValidity()
function without emitEvent
flag, which then calls
this._valueChanges.emit(this._value);
this triggers valueChanges
emitter of the form and you see in console:
Form.valueChanges: Object { input2: null }
this is triggered by form and not by the default
input field controller.
In order to stop ancestor updating, we just need to provide additional flag - onlySelf: true
, which tells to update only its self and not ancestors.
So, in each call to disable()
or enable()
functions where you do not want to update ancestors add this flag:
disable(){
this.form.disable({
onlySelf: true,
emitEvent: false
});
}
disableWEvent(){
this.form.disable({
onlySelf: true
});
}
enable(){
this.form.enable({
onlySelf: true,
emitEvent: false
});
}
enableWEvent(){
this.form.enable({
onlySelf: true
});
}
disableCtrl(){
this.formControl.disable({
onlySelf: true,
emitEvent: false
});
}
disableCtrlWEvent(){
this.formControl.disable({
onlySelf: true
});
}
enableCtrl(){
this.formControl.enable({
onlySelf: true,
emitEvent: false
});
}
enableCtrlWEvent(){
this.formControl.enable({
onlySelf: true
});
}
this will solve the problem for leaf formControls (controls without children), but this line
this._forEachChild(function (control) { control.disable({ onlySelf: true }); });
will call disable
(or enable
) function without passed emitEvent: false
. It looks like angular bug, so, as workaround, we can override these two functions.
First import AbstractControl
:
import {ReactiveFormsModule, FormBuilder, FormControl, Validators, AbstractControl} from '@angular/forms'
than override both functions:
// OVERRIDE disable and enable methods
// https://github.com/angular/angular/issues/12366
// https://github.com/angular/angular/blob/c59c390cdcd825cca67a422bc8738f7cd9ad42c5/packages/forms/src/model.ts#L318
AbstractControl.prototype.disable = function (opts) {
if (opts === void 0) { opts = {}; }
this._status = 'DISABLED';
this._errors = null;
this._forEachChild(function (control) {
control.disable(Object.assign(opts, {onlySelf: true}));
});
this._updateValue();
if (opts.emitEvent !== false) {
this._valueChanges.emit(this._value);
this._statusChanges.emit(this._status);
}
this._updateAncestors(!!opts.onlySelf);
this._onDisabledChange.forEach(function (changeFn) { return changeFn(true); });
};
AbstractControl.prototype.enable = function (opts) {
if (opts === void 0) { opts = {}; }
this._status = 'VALID';
this._forEachChild(function (control) {
control.enable(Object.assign(opts, {onlySelf: true}));
});
this.updateValueAndValidity({ onlySelf: true, emitEvent: opts.emitEvent });
this._updateAncestors(!!opts.onlySelf);
this._onDisabledChange.forEach(function (changeFn) { return changeFn(false); });
};
updated plunker: https://plnkr.co/edit/IIaByz4GlBREj2X9EKvx?p=preview