FIXED: This issue was a bug that is now fixed in versions above 6.0.0 and 5.2.5, according to the GH issue: github.com/angular/angular/issues/12366
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
The ValueChanges is an event raised by the Angular forms whenever the value of the FormControl, FormGroup or FormArray changes. It returns an observable so that you can subscribe to it. The observable gets the latest value of the control.
So basically when we call setValue, patchValue, disable, enable control it triggers value change by default. If we had not setted emitEvent to false, it would trigger valueChange so parent form would trigger unnecessary api which we want to avoid here.
The submit event triggers when the form is submitted, it is usually used to validate the form before sending it to the server or to abort the submission and process it in JavaScript.
SetValue Vs PatchValue The difference is that with setValue we must include all the controls, while with the patchValue you can exclude some controls.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With