我对自己的表格有点疑问。
我做了一个自定义元素:
<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,但有点不想知道任何事情,只是说:
ERROR Error: No value accessor for form control with name: 'surveyType'
我尝试添加ngDefaultControl但没有成功。 这似乎是因为没有输入/选择......但我不知道该怎么做。
我想将我的点击绑定到此formControl,以便当有人点击推送我的&#39;类型&#39;进入formControl。有可能吗?
答案 0 :(得分:163)
您只能在实施ControlValueAccessor
的指令上使用formControlName
。
因此,为了做你想做的事,你必须创建一个实现ControlValueAccessor
的组件,这意味着实现以下三个功能:
writeValue
(告诉Angular如何将模型中的值写入视图)registerOnChange
(注册视图更改时调用的处理函数)registerOnTouched
(注册在组件接收触摸事件时要调用的处理程序,对于了解组件是否已被聚焦有用)。然后,你必须告诉Angular这个指令是一个ControlValueAccessor
(因为当TypeScript编译成JavaScript时它被从代码中剥离,所以界面不会被删除)。您可以通过注册提供商来执行此操作。
提供商应提供NG_VALUE_ACCESSOR
和use an existing value。你还需要一个forwardRef
。请注意,NG_VALUE_ACCESSOR
应为multi provider。
例如,如果您的自定义指令名为MyControlComponent,则应在传递给@Component
装饰器的对象内的以下行中添加一些内容:
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => MyControlComponent),
}
]
您的组件已准备好使用。使用template-driven forms,ngModel
绑定现在可以正常使用。
使用reactive forms,您现在可以正常使用formControlName
,表单控件将按预期运行。
答案 1 :(得分:40)
我认为您应该在input
上使用div
而不是Picasso.with(mContext)
.load(url)
.networkPolicy(NetworkPolicy.OFFLINE)
.into(imageView);
答案 2 :(得分:11)
该错误表示,将formControl
放在div
上时,Angular不知道该怎么办。
要解决此问题,您有两个选择。
formControlName
放在了Angular支持的元素上。它们是:input
,textarea
和select
。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
}
那么各个部分在做什么?
ControlValueAccessor
接口ControlValueAccessor
接口onChange
和onTouch
的方法,以便您可以调用这些函数。因此,了解这一点很重要:您不需要自己实现onChange和onTouch (最初的空实现除外)。您对(c)所做的唯一一件事就是让Angular将其自身的函数附加到您的类上。为什么?因此,您可以在适当的时候调用 Angular提供的onChange
和onTouch
方法。我们将在下面看到它的工作原理。writeValue
方法时,我们还将在下一节中看到它的工作方式。我已将其放在此处,因此ControlValueAccessor
上的所有必需属性都已实现,并且您的代码仍然可以编译。 writeValue
的作用是,在外部更改表单控件时,在自定义组件内进行操作。因此,例如,如果您已经命名了自定义表单控件组件app-custom-input
,并且您将在父组件中使用它,如下所示:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
然后,只要父组件以某种方式更改writeValue
的值,就会触发myFormControl
。例如,这可以在表单(this.form = this.formBuilder.group({myFormControl: ""});
)初始化期间或在表单重置this.form.reset();
上进行。
如果表单控件的值在外部发生更改,通常要执行的操作是将其写入表示表单控件值的局部变量。例如,如果您的CustomInputComponent
围绕基于文本的表单控件,则它看起来可能像这样:
writeValue(input: string) {
this.input = input;
}
以及CustomInputComponent
的html中:
<input type="text"
[ngModel]="input">
您还可以按照Angular文档中的描述将其直接写入输入元素。
现在,您已经处理了外部发生某些更改时组件内部发生的情况。现在让我们看看另一个方向。当组件内部发生某些变化时,如何通知外界?
下一步是通知父组件CustomInputComponent
内部的更改。这是上方(c)中的onChange
和onTouch
函数起作用的地方。通过调用这些函数,您可以将组件内部的更改告知外部。为了将值的更改传播到外部,您需要以新值作为参数调用onChange 。例如,如果用户在自定义组件的input
字段中键入内容,则您将使用更新后的值调用onChange
:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
如果再次从上方检查实现(c),您将看到发生了什么:将其自身的实现与Angular绑定到onChange
类属性。该实现期望一个参数,即更新后的控制值。您现在正在做的就是调用该方法,从而使Angular知道更改。 Angular现在将继续并在外部更改表单值。这是所有这一切的关键部分。 您通过调用onChange
来告知Angular何时应更新表单控件以及使用什么值。您已经为它提供了“访问控制值”的方法。
顺便说一句:onChange
是我选择的。您可以在此处选择任何内容,例如propagateChange
或类似名称。不管您如何命名,它都是一个带有Angular提供的参数的函数,该参数在运行时由registerOnChange
方法绑定到您的类。
由于可以“触摸”表单控件,因此您还应为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
答案 3 :(得分:-1)
对我来说,这是由于选择输入控件上的“ 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>
更多详细信息,请参考Official Docs