web-dev-qa-db-fra.com

ValueChanges sur FormControl se déclenche lorsque Form.enable, même avec emitEvent: false

Avec Angular (4.x) j'utilise ReactiveForms et je me suis abonné à valueChanges sur mon FormControl ("input") comme ceci:

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));
  }

Alors maintenant, je peux réagir aux changements sur la valeur de mon FormControl, mais je remplis bien sûr les valeurs du formulaire quelque part pour commencer, donc j'utilise form.patchValue(data) pour le faire.

Comme ce n'est pas un changement d'utilisateur, je ne veux pas y réagir, alors ajoutez le drapeau emitEvent: false, Comme: this.form.patchValue(data, {emitEvent: false}).

Qui fonctionne comme prévu.

Maintenant, j'ai une certaine logique que lorsque le formulaire est en cours de chargement, j'ai défini l'ensemble du formulaire sur désactivé, this.form.disable({ emitEvent: false }), et lorsque le chargement est terminé, il définit à nouveau le formulaire sur activé: this.form.disable({ emitEvent: false })

Mais j'ai également une logique qui, selon différents drapeaux, définit FormControl sur activé/désactivé: this.formControl.enable( {emitEvent: false});


Le problème que je vois maintenant est que lorsque le Form change d'état, il déclenche le FormControl.valueChanges, même si je fournis l'indicateur emitEvent: false.

Est-ce le comportement attendu ou un bug? Je m'attendais à ce qu'aucun événement ne soit déclenché lors de la fourniture du drapeau?

J'ai fait un plunk où cela peut être testé ici: https://plnkr.co/edit/RgyDItYtEfzlLVB6P5f3?p=preview

16
Bulan

les fonctions disable() et enable() ( 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); });
};

avoir appel à:

this._updateAncestors(!!opts.onlySelf);

qui appelle la fonction updateValueAndValidity() de son parent sans l'indicateur emitEvent, qui appelle ensuite

this._valueChanges.emit(this._value);

cela déclenche valueChanges émetteur du formulaire et vous voyez dans la console:

Form.valueChanges: Object { input2: null }

cela est déclenché par le formulaire et non par le contrôleur de champ d'entrée default. Afin d'arrêter la mise à jour des ancêtres, nous avons juste besoin de fournir un indicateur supplémentaire - onlySelf: true, Qui indique de ne mettre à jour que lui-même et non les ancêtres. Ainsi, dans chaque appel aux fonctions disable() ou enable() où vous ne souhaitez pas mettre à jour les ancêtres, ajoutez ce drapeau:

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
  });
}

cela résoudra le problème des feuilles formControls (contrôles sans enfants), mais cette ligne

this._forEachChild(function (control) { control.disable({ onlySelf: true }); });

appellera la fonction disable (ou enable) sans passer emitEvent: false. Il ressemble à angular angulaire, donc, comme solution de contournement, nous pouvons remplacer ces deux fonctions. Importez d'abord AbstractControl:

import {ReactiveFormsModule, FormBuilder, FormControl, Validators, AbstractControl} from '@angular/forms'

que remplacer les deux fonctions:

// 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); });
};

plunker mis à jour: https://plnkr.co/edit/IIaByz4GlBREj2X9EKvx?p=preview

17
Andriy