【问题标题】:Forwarding formControlName to inner component in Angular with Reactive Forms使用反应式表单将 formControlName 转发到 Angular 中的内部组件
【发布时间】:2019-03-24 09:19:17
【问题描述】:

我有一个自定义控件组件<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

【问题讨论】:

    标签: angular angular-reactive-forms


    【解决方案1】:

    您可以在组件ext-some-input中创建@Input

    ext-some-input.ts

    @Input 
    formControlName
    

    ext-some-input.html

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

    【讨论】:

    • 这对我产生以下错误:No value accessor for form control with name: '...'
    【解决方案2】:

    我认为您实际上正在寻找比input 更复杂的东西。

    如果您想创建自定义输入,您可以创建一个将成为ControlValueAccessor 的类(自定义表单控件)。

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

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

    【讨论】:

    • &lt;some-input&gt; 已经支持响应式表单。我应该在表单和SomeInput 之间实现额外的桥(ControlValueAccessor)以使它们一起工作吗?
    • 我认为这是正确的做法,但您最终能否在 stackblitz 上进行小型复制?这样会更容易提供帮助
    • 我用这个问题创建了 stackblitz 项目:here
    【解决方案3】:

    我遇到过类似的情况,我不想将 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>
    

    【讨论】:

      【解决方案4】:

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

      错误:formControlName 必须与父 formGroup 指令一起使用。

      为了将您的控件与父 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

      【讨论】:

      • 非常感谢!这绝对是我需要的。我会以强制方式了解viewProviders
      • 只是想为偶然发现此答案的任何人注意一些事情...它可以工作,但是如果您使用 formControlName 作为输入变量名,则会在控制台中出现错误。只需为中间组件使用不同的输入变量名称,例如“controlName”。
      • 这似乎有效,但是一旦您检查控制台,您就会发现以下错误:ERROR Error: No value accessor for form control with name: 'someday'。请。修复,否则其他用户会抓住一个错误的例子。
      • @yurzui,有没有办法改变 date-wrapper.component.ts 文件中this.form.someday 的值?
      • @yurzui 在使用 FormArray 时,您将如何解决同样的问题?我尝试实施您的解决方案,但得到 Cannot find control with name: 错误。 StackBlitz:stackblitz.com/edit/ng-tie-reactive-forms-throgh-wrapper-ajj7eg
      【解决方案5】:

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

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

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

      所有内容都在自述文件中进行了解释,并且在 /src 文件夹中提供了一个完整的示例。 (lib 实际上是在 projects/ngx-sub-form 中)。

      这里也有现场演示:https://cloudnc.github.io/ngx-sub-form

      【讨论】:

        【解决方案6】:

        我遇到了几乎相同的场景和问题。 Todd Motto 的 article 让我松了一口气。

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

        通常情况下,例子自己说得更多。

        表单组件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'));
        }
        

        表单组件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>
        

        自定义输入组件 ts(表单-文本)

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

        自定义输入组件 html(表单-文本)

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

        【讨论】:

        • 正是我需要的。谢谢
        猜你喜欢
        • 1970-01-01
        • 2021-09-21
        • 2017-02-01
        • 1970-01-01
        • 2019-11-25
        • 1970-01-01
        • 1970-01-01
        • 2021-01-12
        • 1970-01-01
        相关资源
        最近更新 更多