Angular4-没有用于表单控制的值访问器

2020/11/13 01:22 · javascript ·  · 0评论

我有一个自定义元素:

<div formControlName="surveyType">
  <div *ngFor="let type of surveyTypes"
       (click)="onSelectType(type)"
       [class.selected]="type === selectedType">
    <md-icon>{{ type.icon }}</md-icon>
    <span>{{ type.description }}</span>
  </div>
</div>

当我尝试添加formControlName时,出现错误消息:

错误错误:名称为“ surveyType”的表单控件没有值访问器

我尝试添加ngDefaultControl失败。似乎是因为没有输入/选择...,我不知道该怎么办。

我想将我的点击绑定到此formControl,以便当有人单击整个卡片时会将我的“类型”推入formControl。可能吗?

formControlName只能在实现的指令上使用ControlValueAccessor

实施界面

因此,为了执行您想要的操作,您必须创建一个实现的组件ControlValueAccessor,这意味着实现以下三个功能

  • writeValue (告诉Angular如何将模型中的值写入视图)
  • registerOnChange (注册一个在视图更改时调用的处理程序函数)
  • registerOnTouched (注册一个在组件接收到触摸事件时要调用的处理程序,对于了解组件是否已聚焦很有用)。

注册提供商

然后,您必须告诉Angular该指令是一个ControlValueAccessor(接口不会削减它,因为当TypeScript编译为JavaScript时,它已从代码中剥离)。您可以通过注册提供者来实现

提供者应提供NG_VALUE_ACCESSOR使用现有值您还需要forwardRef这里。注意NG_VALUE_ACCESSOR应该是一个多提供者

例如,如果您的自定义指令名为MyControlComponent,则应在传递给@Componentdecorator的对象内的以下几行中添加一些内容

providers: [
  { 
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: forwardRef(() => MyControlComponent),
  }
]

用法

您的组件已准备就绪,可以使用。使用模板驱动的表单ngModel绑定现在可以正常工作。

使用反应式表单,您现在可以正确使用formControlName,并且表单控件将按预期运行。

资源资源

我认为您应该formControlName="surveyType"input而不是上使用div

该错误意味着,将放在formControl时Angular不知道该怎么办div要解决此问题,您有两个选择。

  1. 您将放在formControlNameAngular支持的开箱即用的元素上。这些都是:inputtextareaselect
  2. 您实现ControlValueAccessor接口。这样,您将告诉Angular“如何访问控件的值”(因此命名)。或简单来说:当您将一个formControlName元素放置在元素上时,自然没有与其关联的值。

现在,实现ControlValueAccessor接口起初可能有些艰巨。尤其是因为那里没有足够好的文档,因此您需要在代码中添加很多样板。因此,让我尝试通过一些简单易懂的步骤将其分解。

将表单控件移到其自己的组件中

为了实现ControlValueAccessor,您需要创建一个新组件(或指令)。将与表单控件相关的代码移到此处。这样,它也将易于重用。首先,可能是要在组件内部拥有控件的原因,这就是为什么需要实现ControlValueAccessor接口的原因,因为否则,您将无法将自定义组件与Angular表单一起使用。

将样板添加到您的代码中

ControlValueAccessor接口的实现非常冗长,这是它附带的样板:

import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // a) copy paste this providers property (adjust the component name in the forward ref)
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // c) copy paste this code
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // d) copy paste this code
  writeValue(input: string) {
    // TODO
  }

那么各个部分在做什么呢?

  • a)让Angular在运行时知道您实现了ControlValueAccessor接口
  • b)确保您正在实现ControlValueAccessor接口
  • c)这可能是最令人困惑的部分。基本上,您正在做的是,为Angular提供重写类属性/方法的方法onChangeonTouch并在运行时使用它自己的实现,这样您便可以调用这些函数。因此,了解这一点很重要:您不需要自己实现onChange和onTouch(最初的空实现除外)。您对(c)所做的唯一一件事就是让Angular将其自身的函数附加到您的类上。为什么?因此,您可以在适当的时候调用Angular提供onChangeonTouch方法。我们将在下面查看其工作原理。
  • d)writeValue当我们实现方法时,我们还将在下一部分中看到该方法的工作方式。我已经将其放在此处,因此所有必需的属性ControlValueAccessor都已实现,并且您的代码仍然可以编译。

实现writeValue

是什么writeValue呢,就是做你的自定义组件里面的东西,当窗体控件在外面改变因此,例如,如果您已命名自定义表单控件组件,app-custom-input并且将在父组件中使用它,如下所示:

<form [formGroup]="form">
  <app-custom-input formControlName="myFormControl"></app-custom-input>
</form>

然后writeValue在父组件以某种方式更改的值时触发myFormControl例如,这可以在form(this.form = this.formBuilder.group({myFormControl: ""});初始化期间或在窗体重置时进行this.form.reset();

如果表单控件的值在外部发生更改,通常要执行的操作是将其写入表示表单控件值的局部变量。例如,如果您CustomInputComponent围绕基于文本的表单控件,它可能看起来像这样:

writeValue(input: string) {
  this.input = input;
}

并在的html中CustomInputComponent

<input type="text"
       [ngModel]="input">

您还可以按照Angular文档中的描述将其直接写入输入元素。

现在,您已经处理了外部发生某些更改时组件内部发生的情况。现在让我们看看另一个方向。当组件内部发生某些变化时,如何通知外界?

调用onChange

下一步是通知父组件有关您内部的更改CustomInputComponent这就是上面(c)中的onChangeonTouch功能起作用的地方。通过调用这些函数,您可以将组件内部的更改告知外部。为了将值的更改传播到外部,您需要使用新值作为参数调用onChange例如,如果用户input在自定义组件字段中键入内容,则onChange使用更新后的值进行调用

<input type="text"
       [ngModel]="input"
       (ngModelChange)="onChange($event)">

如果再次从上方检查实现(c),您将看到发生了什么:Angular将其自己的实现绑定到onChangeclass属性。该实现期望一个参数,即更新后的控制值。您现在正在执行的是调用该方法,从而使Angular知道更改。Angular现在将继续并在外部更改表单值。这是所有这一切的关键部分。您通过调用告诉Angular何时应该更新表单控件以及使用什么值onChange您已为它提供了“访问控制值”的方法。

顺便说一句:这个名字onChange是我选择的。您可以在这里选择任何东西,例如propagateChange类似的东西。无论您如何命名,它都将是一个带有一个参数的函数,该函数由Angular提供,并registerOnChange在运行时由该方法绑定到您的类

呼叫onTouch

由于可以“触摸”表单控件,因此您还应为Angular提供了解何时触摸自定义表单控件的方法。您猜对了,可以通过调用onTouch函数来实现。因此,对于我们的示例,如果您想与Angular对开箱即用的表单控件的操作方式保持一致,则应onTouch在输入字段模糊时调用

<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">

同样,onTouch是我选择的名称,但是它的实际功能由Angular提供,它接受零个参数。这是有道理的,因为您只是让Angular知道了表单控件已被触摸。

放在一起

那么,当它们组合在一起时,它看起来如何?它看起来应该像这样:

// custom-input.component.ts
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // Step 3: Copy paste this stuff here
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  input: string;
  writeValue(input: string) {
    this.input = input;
  }

  // Step 5: Handle what should happen on the outside, if something changes on the inside
  // in this simple case, we've handled all of that in the .html
  // a) we've bound to the local variable with ngModel
  // b) we emit to the ouside by calling onChange on ngModelChange

}
// custom-input.component.html
<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>

// OR

<form [formGroup]="form" >
  <app-custom-input formControlName="myFormControl"></app-custom-input>
</form>

更多例子

嵌套表格

请注意,“控制值访问器”不是嵌套表单组的正确工具。对于嵌套表单组,您可以简单地使用@Input() subform控制值访问器的意思是包装controls,而不是包装groups请参阅以下示例,如何将输入用于嵌套表单:https : //stackblitz.com/edit/angular-nested-forms-input-2

资料来源

对我来说,这是由于选择输入控件上的“ multiple”属性所致,因为Angular对于此类控件具有不同的ValueAccessor。

const countryControl = new FormControl();

在模板内部使用像这样

    <select multiple name="countries" [formControl]="countryControl">
      <option *ngFor="let country of countries" [ngValue]="country">
       {{ country.name }}
      </option>
    </select>

更多详细信息,请参阅官方文档

本文地址:http://javascript.askforanswer.com/angular4-meiyouyongyubiaodankongzhidezhifangwenqi.html
文章标签: ,   ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!