Angular:使用组合的ControlValueAccessor实现嵌套表单

5
介绍了一个在 Angular Connect 2017 演示中实现嵌套表单的 ControlValueAccessor 组合方法。

https://docs.google.com/presentation/d/e/2PACX-1vTS20UdnMGqA3ecrv7ww_7CDKQM8VgdH2tbHl94aXgEsYQ2cyjq62ydU3e3ZF_BaQ64kMyQa0INe2oI/pub?slide=id.g293d7d2b9d_1_1532

在这个演示中,演讲者展示了一种实现自定义表单控件的方法。该控件具有多个值(不仅是单个字符串值,而且还有两个字符串字段,比如街道和城市)。我想实现它,但是遇到了困难。样例应用程序在这里,有人知道我应该纠正什么吗?

https://stackblitz.com/edit/angular-h2ehwx

父组件
@Component({
  selector: 'my-app',
  template: `
    <h1>Form</h1>
    <form [formGroup]="form" (ngSubmit)="onSubmit(form.value)" novalidate>
      <label>name</label>
      <input formControlName="name">
      <app-address-form formControlName="address"></app-address-form>
      <button>submit</button>
    </form>
  `,
})
export class AppComponent  {
  @Input() name: string;
  submitData = '';
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = fb.group({
      name: 'foo bar',
      address: fb.group({
        city: 'baz',
        town: 'qux',
      })
    });
  }

  onSubmit(v: any) {
    console.log(v);
  }
}

嵌套表单组件
@Component({
  selector: 'app-address-form',
  template: `
    <div [formGroup]="form">
      <label>city</label>
      <input formControlName="city" (blur)="onTouched()">
      <label>town</label>
      <input formControlName="town" (blur)="onTouched()">
    </div>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => AddressFormComponent)
    }
  ]
})
export class AddressFormComponent implements ControlValueAccessor {
  form: FormGroup;

  onTouched: () => void = () => {};

  writeValue(v: any) {
    this.form.setValue(v, { emitEvent: false });
  }

  registerOnChange(fn: (v: any) => void) {
    this.form.valueChanges.subscribe(fn);
  }

  setDisabledState(disabled: boolean) {
    disabled ? this.form.disable() : this.form.enable();
  }

  registerOnTouched(fn: () => void) {
    this.onTouched = fn;
  }
}

我收到的错误信息

ERROR TypeError: Cannot read property 'setValue' of undefined
at AddressFormComponent.writeValue (address-form.component.ts:32)
at setUpControl (shared.js:47)
at FormGroupDirective.addControl (form_group_directive.js:125)
at FormControlName._setUpControl (form_control_name.js:201)
at FormControlName.ngOnChanges (form_control_name.js:114)
at checkAndUpdateDirectiveInline (provider.js:249)
at checkAndUpdateNodeInline (view.js:472)
at checkAndUpdateNode (view.js:415)
at debugCheckAndUpdateNode (services.js:504)
at debugCheckDirectivesFn (services.js:445)

我认为FormGroup实例应该以某种方式注入到嵌套的表单组件中...

你能在问题中发布你的代码吗?你遇到了什么具体的问题? - roelofs
@roelofs 谢谢,我已经更新了帖子。 - adwd
2个回答

2

有几个问题,对于你的AppComponent,请将FormBuilder更改为:

this.form = fb.group({
  name: 'foo bar',
  address: fb.control({ //Not using FormGroup
    city: 'baz',
    town: 'qux',
  })
});

在您的AddressFormComponent上,您需要这样初始化您的FormGroup:
form: FormGroup = new FormGroup({
    city: new FormControl,
    town: new FormControl
});

这是您示例的分支: https://stackblitz.com/edit/angular-np38bi

1
哇,我必须使用fb.control而不是fb.group。我感到很奇怪,因为它的实例被引用为FormGroup,但它确实起作用了!谢谢@12seconds。 - adwd
fmm,如果FormArray嵌套,则会出现错误。问题出在哪里?https://stackblitz.com/edit/angular-krhrcz - adwd
1
@penleychan,验证出现了问题。如果我们将嵌套表单视为FormControl,则主表单不知道嵌套表单的有效性。这是演示 https://stackblitz.com/edit/angular-pmfq5z?file=app%2Fapp.component.ts。尝试清除必填字段。我们该如何解决这个问题? - Taras Hupalo
@TarasHupalo,我建议你查看我的其他答案以实现这个目标,如果你https://stackoverflow.com/a/53053333/6736888 - penleychan
@Manoj,这种情况有点不同,更多的是创建自己的ControlValueAccessor。我建议采用另一种方法,链接在你的评论上方。 - penleychan
显示剩余2条评论

0
在工作中,我们遇到了一个问题并尝试了几个月:如何正确处理嵌套表单。实际上,ControlValueAccessor似乎是解决问题的方法,但我们发现它非常冗长,构建嵌套表单需要很长时间。由于我们在应用程序中经常使用这种模式,因此我们花了一些时间进行调查并尝试提出更好的解决方案。我们称之为ngx-sub-form,它是一个可在NPM(+源代码在Github上)上获得的存储库。
基本上,要创建子表单,您只需扩展我们提供的类并传递您的FormControls即可。
我们已更新我们的代码库以使用它,并且我们绝对对此感到满意,因此您可能希望尝试并查看它的效果:)所有内容都在github上的README中有详细说明。

PS: 我们还有一个完整的演示在这里运行 https://cloudnc.github.io/ngx-sub-form


2
我可以接受被踩,但如果您能解释一下为什么您认为我的回答不好,那就更好了。 - maxime1992
你好。在子组件中,验证并没有完全生效。当点击提交时,必需的mat-form-field没有被突出显示。我希望即使表单无效,也能够点击提交按钮,这样你就可以清楚地看到哪些表单没有填写。 你有什么解决这个问题的建议吗?我尝试了使用CVA,但它也不起作用。 一个解决方案是为子组件创建一个ViewChild,创建一个函数,以便子组件可以调用它的form markAllTouched函数。但我觉得这有点hacky。 - Levi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接