在自定义 Angular 组件中访问 FormControl

Posted

技术标签:

【中文标题】在自定义 Angular 组件中访问 FormControl【英文标题】:Access FormControl inside custom Angular Component 【发布时间】:2020-04-18 04:44:59 【问题描述】:

我正在创建一个自定义角度组件,当我的 FormControl(反应式表单)无效时,它会显示错误工具提示。 但我不知道如何访问自定义组件中的 FormControl 以检查它是否被标记为有效。

我想要完成的事情

<div [formGroup]="form">
     <input formControlName="name" type="text" />
     <custom-validation-message formControlName="name">My special error message!</custom-validation-message>
  </div>

已经遇到的东西

错误错误:表单控件没有值访问器,名称为:'surveyType'

通过使用 NG_VALUE_ACCESSOR 实现 ControlValueAccessor 来解决此问题,即使我不想更改该值。我还添加了一个注入器来访问 NgControl。

import  Component, OnInit, Injector  from '@angular/core';
import  NgControl, ControlValueAccessor, NG_VALUE_ACCESSOR  from '@angular/forms';

@Component(
    selector: 'custom-validation-message',
    templateUrl: './validation-message.component.html',
    providers: [
        provide: NG_VALUE_ACCESSOR, multi: true, useExisting: ValidationMessageComponent
    ]
)
export class ValidationMessageComponent implements ControlValueAccessor, OnInit 
    public formControl: any;

    constructor(private injector: Injector) 
        super();
    

    public ngOnInit(): void 
        const model = this.injector.get(NgControl);
        this.formControl = model.control;
    

    public writeValue(obj: any): void 
    
    public registerOnChange(fn: any): void 
    
    public registerOnTouched(fn: any): void 
    
    public setDisabledState?(isDisabled: boolean): void 
    

当前问题 model.control 未定义。检查模型后,我发现模型和空一样好,只有 _parent 是我表单的完整表示。 model._parent.controls 确实包含我表单的所有控件。但是我还是不知道当前的控件。

【问题讨论】:

【参考方案1】:

您可以通过以下方式访问自定义 FormControl 组件 (ControlValueAccessor) 的 FormControl。使用 Angular 8 测试。

<my-text-input formControlName="name"></my-text-input>
@Component(
  selector: 'my-text-input',
  template: '<input
    type="text"
    [value]="value"
  />'
)
export class TextInputComponent implements AfterContentInit, ControlValueAccessor 

  @Input('value') value = '';

  // There are things missing here to correctly implement ControlValueAccessor, 
  // but it's all standard.

  constructor(@Optional() @Self() public ngControl: NgControl) 
    if (ngControl != null) 
      ngControl.valueAccessor = this;
    
  


  // It's important which lifecycle hook you try to access it.
  // I recommend AfterContentInit, control is already available and you can still
  // change things on template without getting 'change after checked' errors.
  ngAfterContentInit(): void 
    if (this.ngControl && this.ngControl.control) 
      // this.ngControl.control is component FormControl
    
  

【讨论】:

这会导致Error: Circular dep for TextInputComponent 我不明白这是如何导致循环依赖的。它很可能是您的导入中的某些内容。 我注意到您没有提供 NG_VALUE_ACCESSOR providers: [ provide: NG_VALUE_ACCESSOR, useExisting: MyCustomComponent, multi: true ], 是否与您在构造函数中所做的相同并检查它是否为空? 是的,这里解释了material.angular.io/guide/… 这可能是@GennadiyLitvinyuk 有循环部门的原因,他提供了 NG_VALUE_ACCESSOR,在这种情况下不应该提供。【参考方案2】:

正如我告诉你的那样,你只想制作一个显示表单控件验证消息的组件,另一个答案解释了为什么 ControlValueAccessor 不是这里的情况,你只想将控件表单引用传递给组件然后检查验证状态,Thomas Schneiter 的回答是正确的,但我面对这种情况,很难通过 get 方法保持重新访问,有时我们在子组和表单数组中,所以我的想法是将表单控件的名称作为字符串传递然后获取控件引用。

CustomValidationMessageComponent

@Component(
  selector: "custom-validation-message",
  templateUrl: "./custom-validation-message.component.html",
  styleUrls: ["./custom-validation-message.component.css"]
)
export class CustomValidationMessageComponent 
  @Input()
  public controlName: string;

  constructor(@Optional() private controlContainer: ControlContainer)  

  get form(): FormGroup 
    return this.controlContainer.control as FormGroup;
  

  get control(): FormControl 
    return this.form.get(this.controlName) as FormControl;
  

模板

<ng-container *ngIf="control && control?.invalid && control?.touched">
  <ul>
    <li *ngIf="control.hasError('required')">
      this is required field
    </li>
    <li *ngIf="control.hasError('pattern')">
      pattern is invalid 
    </li>
    <li *ngIf="control.hasError('maxlength')">
      the length is over the max limit
    </li>
     <!-- <li *ngFor="let err of control.errors | keyvalue">
       err.key
     </li> -->
  </ul>

</ng-container>

你可以这样使用它

<form [formGroup]="form">
 <label>
   First Name <input type="text" formControlName="firstName" />
   <div>
       <custom-validation-message controlName="firstName"></custom-validation-message>
   </div>
 </label>

 ...

</form>

demo ??

您可以查看JB Nizet 创建的这个角度库ngx-valdemort,它完美地解决了这个问题?。

【讨论】:

我刚刚试用了您的方法,效果很好。我正在更新自己的框架,ngx-valdemort 给了我另一个想法。拥有一个包含所有可能错误的容器比为每个可能的错误复制自定义验证消息要好得多。 @Optional() private controlContainer: ControlContainer 是如何注入的?这是在 formGroup 内部自动发生的吗? @ThomasSchneiter ControlContainer 通过 Angular 注入并允许我们从父级访问 formGroup 实例检查这一点并包括 Kara Ericksons thecodecampus.de/blog/nested-forms-in-angular 的演讲【参考方案3】:

没有检查您的方法。 CustomControlValueAccessor 应该只用于真实的表单控件。这是一种创造性的方法,它可能会以某种方式起作用,但我不会去做。

除了注入之外,还有其他方法可以访问验证组件中的FormControl

1) 定义不带FormBuilderFormGroup,以便您可以直接访问表单控件:

  firstName: new FormControl('');
  lastName: new FormControl('');

  profileForm = new FormGroup(
    firstName,
    lastName
  );

然后在您的 html 中,您可以将表单控件传递给 custom-validation-message:

<custom-validation-message [control]="firstName">My special error message!</custom-validation-message>

2) 仍然使用FormBuilder,但带有getter 函数:

// component

get firstName() 
    return this.profileForm.get('firstName') as FormControl;

<custom-validation-message [control]="firstName">My special error message!</custom-validation-message>

3) 或如 Thomas Schneiter 所写:访问模板中的控件:

<form [formGroup]="form">
   <input formControlName="name" type="text" />
   <custom-validation-message [control]="form.get('firstName)">My special error message!</custom-validation-message>
</form>

【讨论】:

这是我原来的样子。我想改变以摆脱“复杂性”(form.get(...))。这就是为什么我想要一些看起来类似于绑定输入控件的东西。【参考方案4】:

如果我理解正确,&lt;custom-validation-message&gt; 应该只显示响应式表单输入的验证错误。

ControlValueAccessor 用于创建自定义输入。您要做的是创建一个带有抽象控件作为输入的简单组件。该组件可能如下所示:

ts:

@Input() public control: AbstractControl;
...

这样,您可以访问自定义组件内部的 formControls 属性,例如 invalidtouchederrors。 html:

<ng-container *ngIf="control?.invalid && control?.touched">
  <ul>
    <li class="validation-message" *ngFor="let error of control.errors">
      error
    </li>
  </ul>
</ng-container>

然后添加应显示错误的控件作为输入

<custom-validation-message [control]="form.get('name')"></custom-validation-message>

【讨论】:

嗨?,control.errors 是一个键值对象而不是任何数组,所以在这种情况下,您会收到类似 NgFor only supports binding to Iterables such as Arrays. 的错误,您可以像这样解决此问题 &lt;li *ngFor="let err of control.errors | keyvalue"&gt; err.key &lt;/li&gt; ?

以上是关于在自定义 Angular 组件中访问 FormControl的主要内容,如果未能解决你的问题,请参考以下文章

在自定义组件中反应原生访问 refs

在自定义指令 angular 4 中实现 onclick()

如何在自定义控件中添加所需的星号?

在自定义组件上使用 v-for,我可以访问组件内部的 ":key" 的值吗?

在自定义钩子中访问 Redux 状态?

在自定义 Angular 库中使用 Postcss