ValueChanges в FormControl запускает, когда Form.enable, даже с emitEvent: false

С Angular (4.x) я использую ReactiveForms, и я подписался на valueChanges на моем FormControl ("enter") следующим образом:

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

Поэтому теперь я могу реагировать на изменения в значении моего FormControl, но я, конечно, заполняю значения формы откуда-то, чтобы начать с этого, поэтому я использую form.patchValue(data) для этого.

Поскольку это не пользовательский обмен, я не хочу реагировать на него, поэтому добавьте флаг emitEvent: false, например: this.form.patchValue(data, {emitEvent: false}).

Что работает, как ожидалось.

Теперь у меня есть какая-то логика, когда при загрузке формы я отключил this.form.disable({ emitEvent: false }) форму, this.form.disable({ emitEvent: false }), и когда она закончила загрузку, она снова this.form.disable({ emitEvent: false }) всю форму: this.form.disable({ emitEvent: false })

Но у меня также есть логика, которая в зависимости от разных флагов устанавливает, что FormControl включен/отключен: this.formControl.enable( {emitEvent: false});


Проблема, которую я сейчас вижу, заключается в том, что когда Form, изменяет статус, он запускает FormControl.valueChanges, хотя я предоставляю флаг emitEvent: false.

Это ожидаемое поведение или ошибка? Я ожидал, что никакое событие не будет запущено вообще при предоставлении флага?

Я сделал плунж, где это можно протестировать здесь: https://plnkr.co/edit/RgyDItYtEfzlLVB6P5f3?p=preview

Ответ 1

обе функции disable() и enable() (источник кода):

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

позвоните:

this._updateAncestors(!!opts.onlySelf);

который вызывает свою родительскую updateValueAndValidity() без флага emitEvent, который затем вызывает

this._valueChanges.emit(this._value);

это запускает valueChanges эмиттер формы и вы видите в консоли:

Form.valueChanges: Object { input2: null }

это инициируется формой, а не контроллером поля ввода по default. Чтобы остановить обновление предка, нам просто нужно предоставить дополнительный флаг - onlySelf: true, который сообщает обновлять только его "я", а не "предки". Таким образом, в каждом вызове функции disable() или enable() где вы не хотите обновлять предков, добавьте этот флаг:

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

это решит проблему для leaf formControls (элементы управления без детей), но эта строка

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

будет вызывать disable (или enable) без переданного emitEvent: false. Это похоже на угловую ошибку, поэтому, как обходной путь, мы можем переопределить эти две функции. Первый импорт AbstractControl:

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

чем переопределить обе функции:

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

обновленный плункер: https://plnkr.co/edit/IIaByz4GlBREj2X9EKvx?p=preview