FormControl 上的 ValueChanges 在 Form.enable 时触发,即使使用 emitEvent: false

Posted

技术标签:

【中文标题】FormControl 上的 ValueChanges 在 Form.enable 时触发,即使使用 emitEvent: false【英文标题】:ValueChanges on FormControl triggers when Form.enable, even with emitEvent: false 【发布时间】:2017-09-18 09:48:03 【问题描述】:

已修复:根据 GH 的说法,此问题是现在在 6.0.0 和 5.2.5 以上版本中修复的错误 问题:github.com/angular/angular/issues/12366

对于 Angular (4.x),我使用 ReactiveForms,并且我已经订阅了我的 FormControl(“输入”)上的 valueChanges,如下所示:

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 )

但我也有逻辑,根据不同的标志将 FormControl 设置为启用/禁用:this.formControl.enable( emitEvent: false);


我现在看到的问题是,当 Form 更改状态时,它会触发 FormControl.valueChanges,即使我提供了 emitEvent: false标志。

这是预期的行为,还是错误? 我希望在提供标志时根本不会触发任何事件?

我做了一个可以在这里测试的地方: https://plnkr.co/edit/RgyDItYtEfzlLVB6P5f3?p=preview

【问题讨论】:

看来这确实是个bug:github.com/angular/angular/issues/12366 同源,已推送修复,github issue已关闭。 【参考方案1】:

disable()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); );
;

请致电:

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

这将解决叶子 formControls(没有子控件)的问题,但是这一行

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

将调用disable(或enable)函数而不传递emitEvent: false。它看起来像 angular bug,因此,作为解决方法,我们可以覆盖这两个函数。 先导入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); );
;

更新plunker:https://plnkr.co/edit/IIaByz4GlBREj2X9EKvx?p=preview

【讨论】:

一个广泛而好的答案,很好的挖掘,谢谢!只要错误仍然存​​在,就可以将其标记为已回答。 在 6.0.0 和 5.2.5 以上版本中已修复,根据 GH 问题:github.com/angular/angular/issues/12366 是的,我看到了,谢谢。 github.com/angular/angular/blob/master/packages/forms/src/…, github.com/angular/angular/blob/master/packages/forms/src/…...

以上是关于FormControl 上的 ValueChanges 在 Form.enable 时触发,即使使用 emitEvent: false的主要内容,如果未能解决你的问题,请参考以下文章

ngForm 和 FormControl 的区别

是否可以获得formControl的本机元素?

如何禁用 FormGroup 中的所有 FormControl

FormControl 是做啥用的?为啥使用它?应该如何使用?

如何在组件销毁时销毁反应式 FormControl?

Angular2 formControl用于选择多个