Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ValueChanges on FormControl triggers when Form.enable, even with emitEvent: false

Tags:

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

like image 315
Bulan Avatar asked Apr 21 '17 11:04

Bulan


People also ask

What triggers ValueChanges Angular?

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.

Does patchValue trigger ValueChanges?

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.

Which event get triggered when user comes in the form control?

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.

What is the difference between setValue and patchValue?

SetValue Vs PatchValue The difference is that with setValue we must include all the controls, while with the patchValue you can exclude some controls.


1 Answers

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

like image 137
Andriy Avatar answered Sep 27 '22 19:09

Andriy