使用 Reactive Forms 将 formControlName 转发到 Angular 中的内部组件

Forwarding formControlName to inner component in Angular with Reactive Forms

我有一个自定义控件组件 <some-input>,我包装到 <ext-some-input>SomeInput 被封装,拥有自己的 API 并支持反应形式。 ExtSomeInput 创建为 SomeInput.

的高级包装器

我关注html:

<form [formGroup]="form">
    <ext-some-input formControlName="name">
</form>

ExtSomeInput的html:

<some-input formControlName="_???_"></some-input>

问题是如何将formControlName转发给内部SomeInput组件?我需要将表单和内部 formControl 绑定起来。这可能吗?

已编辑:

我创建了带有这个问题的 stackblitz 项目:here

You can create a @Input in component ext-some-input

ext-some-input.ts

@Input 
formControlName

ext-some-input.html

<some-input [formControlName]="formControlName"></some-input>

我认为您实际上看起来比 input 复杂一点。

如果您想创建自定义输入,您可以创建一个 class,这将是一个 ControlValueAccessor(自定义表单控件)。

请查看以下文章,其中解释得非常好:

https://alligator.io/angular/custom-form-control

我 运行 遇到过类似的情况,我不想将 formControlName 传递给包装器组件。我首选的解决方法是在包装器组件中重用从父表单传递的 formGroup。您可以通过在 ExtSomeInput 组件的构造函数中注入 ControlContainer 来做到这一点:

ParentForm.component.html

<form [formGroup]="form">
  <ext-some-input controlName="name">
</form>

ExtSomeInput.component.ts

// pass the formControlName as string to wrapper
@Input public controlName: string;
public form: FormGroup;

constructor(public controlContainer: ControlContainer) {}

ngOnInit() {
  this.form = <FormGroup>this.controlContainer.control;
}

ExtSomeInput.component.html

// use ng-container to omit this from the DOM
<ng-container [formGroup]="form">
  // wrapper markup here
  <some-input [formControlName]="controlName"></some-input>
</ng-container>

您的内部组件可以使用 @Input controlName,但开箱即用:

Error: formControlName must be used with a parent formGroup directive.

为了将您的控件与父 FormGroup 联系起来,您可以定义 viewProvider 如下:

import { Component, Input, OnInit} from '@angular/core';
...
import { ControlContainer, FormGroupDirective } from '@angular/forms';

@Component({
  ...
  viewProviders: [
    {
      provide: ControlContainer,
      useExisting: FormGroupDirective
    }
  ]
})
export class DateWrapperComponent implements OnInit {
    @Input() controlName: string;
}

Forked Stackblitz

如果您不知道作为 ControlContainerFormGroupDirectiveFormGroupNameFormArrayName)提供的确切指令,您可以使用更通用的方法:

viewProviders: [
  {
    provide: ControlContainer,
    useFactory: (container: ControlContainer) => container,
    deps: [[new SkipSelf(), ControlContainer]]
  }
]

Demo

嘿,在这里做第二个回答,因为我们在工作中提出了一种通用方法,我们将其作为开源库发布 =)。

您可以在这里找到它:https://github.com/cloudnc/ngx-sub-form

它应该可以帮助您管理: - 嵌套形式 - 具有多态数据的表单 - 更好的打字

自述文件中解释了所有内容,并在 /src 文件夹中提供了完整示例。 (lib其实在projects/ngx-sub-form).

此处也提供现场演示:https://cloudnc.github.io/ngx-sub-form

我遇到了几乎相同的情况和问题。 Todd Motto 的 article 让我如释重负。

他的方法是将 formGroup 作为自定义输入组件的父级。然后,从该组件,您可以将控件(验证器)和 formControlName(值)绑定到输入标签。

一如往常,榜样自己说话。

Form component ts

// Reactive Forms
form: FormGroup;    

constructor(private formBuilder: FormBuilder) { }

ngOnInit() {
  // Form structure and validators
  this.form = this.formBuilder.group({
    'user' : this.formBuilder.group({
      'username' : ['', Validators.required],
      'email' : ['', [Validators.required, Validators.email]]
    }),
    'identity' : this.formBuilder.group({
      'firstname' : ['', Validators.required],
      'lastname'  : ['', Validators.required],
      'address' : this.formBuilder.group({
        'street' : ['', Validators.required],
        'city'  : ['', Validators.required],
      })
    })
  });        
}

onSubmit() {
    // Get object with same structure as form but only with values
    console.log(this.form.value);
    alert('Form is ' + (this.form.invalid ? 'invalid' : 'valid'));
}

Form component html

<form [formGroup]="form" (ngSubmit)="onSubmit()">
    <form-text [formGroupParent]="form.get(['user'])"
               [formGroupControlName]="'username'">
    </form-text>
    <form-text [formGroupParent]="form.get(['user'])"
               [formGroupControlName]="'email'">
    </form-text>
    <hr>
    <form-text [formGroupParent]="form.get(['identity'])"
               [formGroupControlName]="'firstname'">
    </form-text>
    <form-text [formGroupParent]="form.get(['identity'])"
               [formGroupControlName]="'lastname'">
    </form-text>
    <hr>
    <form-text [formGroupParent]="form.get(['identity','address'])"
               [formGroupControlName]="'street'">
    </form-text>
    <form-text [formGroupParent]="form.get(['identity','address'])"
               [formGroupControlName]="'city'">
    </form-text>
    <button type="submit">Submit</button>
</form>

Custom input component ts (form-text)

// Needed to bind formControlName
@Input() formGroupParent: FormGroup;
@Input() formGroupControlName: string;
// FormControl store validators
control: FormControl;

ngOnInit() {
    // Fetch Form control (validator) from FormGroup parent
    this.control = <FormControl>this.formGroupParent.get(this.formGroupControlName);
}

Custom input component html (form-text)

<ng-container [formGroup]="formGroupParent">
  <label>{{formGroupControlName}}</label> 
  <input type="text" formControlName="{{formGroupControlName}}">
</ng-container>