【问题标题】:Passing a FormControl to a child component - No value accessor for form control with unspecified name将 FormControl 传递给子组件 - 未指定名称的表单控件没有值访问器
【发布时间】:2018-11-01 10:37:53
【问题描述】:

我有一个输入组件customInput,它创建了一个经典的输入字段并为其添加了一些布局香料,没有额外的逻辑。

我想传递一个 formControl 给它,将它绑定到它包含的输入。

应该这样使用:

<form [formGroup]="form">
  <custom-input [formControl]="form.controls['control']"></custom-input>
</form>

内部自定义输入:

export class HidInputFieldComponent  {
   @Input() formControl: AbstractControl

   ...
}

<div class="container">
  <input [formControl]="formControl"/>
    <label>label</label>
</div>

现在当我初始化组件时,我得到了

未指定名称的表单控件没有值访问器

在我的组件构造函数中记录控件,它是未定义的。

是我做错了还是没有办法绕过ControlValueAccessor?由于我实际上并没有构建自定义控件(我仍然使用经典输入),因此看起来很极端

【问题讨论】:

  • 您的输入必须是 @Input() formControl:FormControl,而不是 AbstractControl
  • 没有。上面的代码有效,问题只是这个阻止渲染的控制台错误。如果我强制重新渲染,一切都很好

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


【解决方案1】:

您不需要导入 ControlValueAccessor 或任何类似的东西来完成此操作。

您需要做的就是将 FormControl 对象传递给您的子组件,如下所示:

<form [formGroup]="form">
  <custom-input [control]="form.controls['theControlName']">
  </custom-input>
</form>

这意味着您的自定义输入组件应如下所示:

import {Component, Input} from '@angular/core';
import {FormControl} from '@angular/forms';

@Component({
  selector: 'custom-input',
  templateUrl: './input.component.html',
  styleUrls: ['./input.component.scss']
})
export class InputComponent {
  @Input() control: FormControl;
}

还有模板:

<input [formControl]="control">

就是这样。

如果你像这样实现自定义输入,则不必将父 formGroup 带入子组件逻辑中,那是完全没有必要的(除非你需要对它或表单的其余部分进行一些操作控制)。

此外,将 FormControl 对象传递给自定义输入将使您能够访问它的属性,而无需引用 FormGroup 然后获取特定控件,因为这是在父组件上完成的工作。

我希望这个解决方案有助于简化许多人的工作,因为制作这种自定义控件很常见。

【讨论】:

  • 除了使用 get() "form.get('theControlName')" 你也可以直接访问它 form.controls['theControlName']
【解决方案2】:

来自 Johann Garrido 的this answer 很好,但是它引入了额外的输入 [control],您在使用自定义组件时需要始终牢记这一点。

另外,它直接绑定到ReactiveFormsModule,因为它只接受FormControl 实例。

在我看来,更好的方法是实现ControlValueAccessor 接口,但利用一些变通方法来避免重复控制处理:

export const NOOP_VALUE_ACCESSOR: ControlValueAccessor = {
  writeValue(): void {},
  registerOnChange(): void {},
  registerOnTouched(): void {}
};

并在包装表单控件的组件中使用NOOP_VALUE_ACCESSOR

@Component({
  selector: "wrapped-input",
  template: `
    <mat-form-field class="example-full-width">
      <mat-label>Wrapped input</mat-label>

      <!--We pass NgControl to regular MatInput -->
      <input matInput [formControl]="ngControl.control" />
    </mat-form-field>
  `
})
export class WrappedInput {
  constructor(@Self() @Optional() public ngControl: NgControl) {
    if (this.ngControl) {
      // Note: we provide the value accessor through here, instead of
      // the `providers` to avoid running into a circular import.
      // And we use NOOP_VALUE_ACCESSOR so WrappedInput don't do anything with NgControl
      this.ngControl.valueAccessor = NOOP_VALUE_ACCESSOR;
    }
  }
}

这样 Angular 会认为WrappedInput 的工作方式与任何其他实现ControlValueAccessor 接口的组件(例如MatInput)一样。

另外,它适用于ReactiveFormsModule 和普通FormsModule

WrappedInput 可以这样使用:

<wrapped-input [formControl]="sourceControl"></wrapped-input>

这是您可以玩的完整的 stackblitz: https://stackblitz.com/edit/angular-wrapped-form-control-example?file=src/app/app.ts

【讨论】:

【解决方案3】:

使用FormGroupDirective

该指令接受现有的 FormGroup 实例。届时将 使用此 FormGroup 实例来匹配任何子 FormControl、FormGroup、 和 FormArray 实例到子 FormControlName、FormGroupName 和 FormArrayName 指令。

这样做您可以从父级访问子 formControl

  @Component({
    selector: 'app-custom-input',
    templateUrl: './custom-input.html',
    viewProviders:[{ provide: ControlContainer, useExisting: FormGroupDirective}]
  })    
    export class HidInputFieldComponent  {
     constructor(private fcd:FormGroupDirective) {       
    }
    ngOnInit() {
        this.fcd.form.addControl('formControl',new FormControl(''));
    }
    }

    <div class="container">
      <input [formControl]="formControl"/>
        <label>label</label>
    </div>

【讨论】:

  • 我需要在父组件中定义所有验证器。这似乎不可能,还是我误解了这一点?
  • 您也可以在子表单中进行验证。如果您想了解更多有关嵌套表单的信息,请观看此youtube.com/watch?v=CD_t3m2WMM8
【解决方案4】:

您也可以尝试实现 ControlValueAccessor 接口类。

@Component({
  selector: 'app-custom-input',
  templateUrl: './app-custom-input.component.html'
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: forwardRef(() => HidInputFieldComponent)
    }
  ]
})
export class HidInputFieldComponent implements OnInit, ControlValueAccessor {
  @Output() setDropdownEvent: EventEmitter<any> = new EventEmitter();

  ngOnInit() {}

  writeValue(value: any) {}

  propagateChange(time: any) {
    console.log(time);
  }

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched() {}
}

【讨论】:

  • 我读到过这个,但我想找到它。我认为在我的情况下应该是可能的
  • 这不是我的问题的答案,是吗?
猜你喜欢
  • 1970-01-01
  • 2019-08-17
  • 2019-02-09
  • 2018-10-30
  • 2020-07-16
  • 1970-01-01
  • 1970-01-01
  • 2018-03-24
  • 1970-01-01
相关资源
最近更新 更多