【问题标题】:Change nested form field value from parent component从父组件更改嵌套表单字段值
【发布时间】:2021-09-10 23:23:43
【问题描述】:

假设我有一个实现 CVA(控制值访问器)的嵌套表单。

// Child component which implements CVA
this.addressForm = new FormGroup({
 city: new FormControl()
 postalCode: new FormControl(null)
})
// Parent component html
<form [formGroup]= "productForm">
 <input formControlName="date">
 <address-form formControlName="addressForm"> </address-form> 
</form>

// Parent component ts
 this.productForm = new FormGroup({
  date: new FormControl(null),
  addressForm: new FormControl(null)
});

现在我只想修补 addresForm 的 city 字段,所以我尝试了:

// This will set other values of addressForm to null.
this.productForm.get('addressForm').patchValue({city: 'some-city'});

// This below will not work.
this.productForm.get('addressForm.city').patchValue('some-city');// error no city control.

// Another workaround is using ControlContainer instead to link to parent
Instead of this:
  // this.addressForm = new FormGroup({
  // city: new FormControl()
  // postalCode: new FormControl(null)
  //})
using this on child
this.addressForm = this.controlContainer.control as FormGroup;
and declare formGroup on Parent.
this.productForm = new FormGroup({
  date: new FormControl(null),
  addressForm: new FormGroup({city: new FormControl(null), postalCode: new FormControl(null) })
});

BUT in this workaround, writeValue method is not called on Child.

因为它是 formControlName 保存单个值,其他值被重置。 那么如何在不重置其他字段值的情况下设置嵌套表单字段值呢?

换句话说,我们如何控制来自父级的嵌套表单组件字段,例如修补值或监听特定字段值的变化? Stackblitz URL

【问题讨论】:

  • 能否展示writeValue函数代码?
  • 确定。这里控制台日志现在显示。公共 writeValue(value: TAddressForm): void { console.log('writeValue', value);常量 { 城市,邮政编码} = 价值 || {}; this.formGroup.patchValue({ city, postalCode}, { emitEvent: false }); }
  • 问题是你正在解构 writeValue 参数,当你从发送对象 { city: 'some-city' } 的父级修补值时,JS 在这里找不到 postalCode 并将其设置为 undefined ,所以尝试这样做: public writeValue(value: TAddressForm): void { this.formGroup.patchValue(value || {}, { emitEvent: false }); } 和来自父母的: this.productForm.get('addressForm').patchValue({city: 'some-city'});
  • 很遗憾不是这样,我在这个方法里面调用了console.log('writeValue),在devtools上看不到。
  • 正如我告诉你的,将 writeValue 修补函数更改为 this.addressForm.patchValue(value || {});并且您的表单将正确修补:) stackblitz.com/edit/angular-we8nru 这是我的示例,我也为您准备了

标签: angular angular-reactive-forms angular-forms


【解决方案1】:

粗俗,你在formGroup里面没有formGroup,你只有一个FormGroup,里面有两个FormControl,最后一个存放一个对象。

一般来说,当我们使用 FormGroup 时,我们可以采取两种方法

1.-在父级中定义完整的FormGroup,并将内部的FormGroup传递给子级

this.productForm = new FormGroup({
  date: new FormControl(null),
  addressForm: new FormGroup({ //see that adressForm is a FormGroup
     city: new FormControl()
     postalCode: new FormControl(null)
  })
});

我们的孩子是一个简单的组件 - 没有实现 controlValue Accesor

//declare a variable "form"
form:FormGroup
//in a input asign the variale to the form
@Input('addressForm') _ set(value)
{
   this.form=value as FormGroup
}

<!--we control the formGroup as another formGroup-->
<form [formGroup]="form">
  <input formControlName="city">
  <input formControlName="postalCode">
</form>

我们在父级中使用

<form [formGroup]= "productForm">
 <input formControlName="date">
 <!--see that pass as @Input the formGroup-->
 <address-form [addressForm]="productForm.get('addressForm')"> </address-form> 
</form>

2.-创建管理对象{city:..postalCode:..}的自定义表单控件

export class CustomAddressComponent implements ControlValueAccessor,OnDestroy {
  form: FormGroup = new FormGroup({
    city: new FormControl(),
    postalCode: new FormControl(null)
  });
  subscription:any
  onChange = (obj: any) => {};

  onTouched = () => {};
  writeValue(obj: any) {
    this.form.patchValue(obj);
    this.subscription=this.form.valueChanges.subscribe(res=>this.onChange(res))
  }
  registerOnChange(onChange: any) {
    this.onChange = onChange;
  }

  registerOnTouched(onTouched: any) {
    this.onTouched = onTouched;
  }
  setDisabledState(disabled: boolean) {
    this.form[disabled ? 'disable' : 'enable']();
  }
  ngOnDestroy()
  {
    this.subscription.unsubscribe()
  }

并在父级中使用

  productForm = new FormGroup({
    date: new FormControl(null),
    addressForm: new FormControl(null)  //<--see that is only a FormControl
  });

<form [formGroup]="productForm2">
  <input formControlName="date">
  <!--use formControlName, it's like another input!-->
  <custom-address-form formControlName="addressForm"> </custom-address-form>
</form>

stackblitz 中,您有两种方法。看到 form.value 在两种情况下都具有相同的结构

注意:真的要管理 FormGroup,我更喜欢使用简单的方法(使用组件),仅使用自定义表单控件来“多一些”而不是组 FormControls。

注意2:自定义表单控件制作速度很快,只是为了演示

更新真的有其他方法,我们可以,例如创建我们的表单,如

  productForm = new FormGroup({
    date: new FormControl(null),
    addressForm: new FormGroup({}) //<--an empty FormGroup
  });

我们的组件创建表单

  form:FormGroup
  @Input('addressForm') set _(value:any)
  {
     this.form=value as FormGroup;
     this.form.addControl('city',new FormControl())
     this.form.addControl('postalCode',new FormControl())
  }

甚至使用 FormGroupDirective 来访问一个表单并创建一个类似的表单

  productForm3 = new FormGroup({
    date: new FormControl(null), //<--see that we don't add anything more
  });

并使用类似的组件

  form: FormGroup;
  constructor(
    @Host() private formGroupDirective: FormGroupDirective,
    @Attribute('groupName') private name
  ) {}
  ngOnInit() {
    setTimeout(() => {
      this.formGroupDirective.form.addControl(
        this.name,
        new FormGroup({
          city: new FormControl(),
          postalCode: new FormControl()
        })
      );
      console.log(this.formGroupDirective.form.value);
      this.form = this.formGroupDirective.form.get(this.name) as FormGroup;
    });
  }

还有我们的父组件:

<form [formGroup]="productForm3">
  <input formControlName="date">
  <!--see that only put our component yet add the formGroup-->
  <address-form-three groupName='addressForm'> </address-form-three>
</form>

我用这两个案例更新了 stackblitz

注意:我想记得另一种方式,但我找不到链接:(

【讨论】:

  • 您好,Eliseo。首先感谢详细解答。方法1:不幸的是,这不适合。原因:我们如何监听表单是否被禁用/启用的事件?在 CVA 中,我们有 setDisabledState() Apporach 2:这是我正在使用的当前方法,问题是我无法更改嵌套表单的特定值。请看问题。它重置其他嵌套表单值。
  • 你不能使用 this.productForm.get('addressForm.city') 因为你有没有控制“城市”。您可以使用this.productForm.get('adressForm').setValue({city:'your-city'}) - 但您会丢失邮政编码-。关于第一种方法,您可以从父级或子级禁用/启用。 FormControls 相同,例如,来自父:this.productForm.get('address.city').disable()this.productForm.get('addressForm').disable(); 在子中,您可以使用this.form.get('city').disable()this.form.disable()
  • Eliseo,是的,我同意你的看法,行为是一样的,但问题是我们无法收听禁用状态。例如,当表单启用时,子组件会做一些工作。解决方法是将 isDisabled 作为 Input 传递并通过 onChanges 生命周期进行侦听。另一个大问题是,在这种情况下,如果我们在 Child 上有 valueChange 侦听器可能会中断,因为子组件是按条件渲染的,所以如果我在渲染子值时更改子值(afterViewInit),则会在父组件上抛出错误,例如无法修补未定义的值。
  • 更新:似乎通过 formGroup 也可以解决问题。将发布更新。谢谢埃利塞奥!
  • 似乎方法 2 整体适合我。缺点之一是,您应该确保正确的字段名称,因为在父组件上声明并且您也在子组件上使用。
猜你喜欢
  • 1970-01-01
  • 2021-04-25
  • 2019-03-27
  • 2015-10-24
  • 1970-01-01
  • 2023-04-03
  • 2021-08-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多