上放置
formControl
时,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'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor {
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
writeValue(input: string) {
}
那么这些单独的部分都在做什么呢?
- a) 在运行时告诉 Angular 你已经实现了
ControlValueAccessor
接口
- b) 确保你正在实现
ControlValueAccessor
接口
- c) 这可能是最令人困惑的部分。基本上你正在做的是,你让 Angular 在运行时覆盖你的类属性/方法
onChange
和 onTouch
,以便你可以调用这些函数。所以这一点很重要: 你不需要自己实现 onChange 和 onTouch(除了最初的空实现)。你使用 (c) 的唯一目的就是让 Angular 将它自己的函数附加到你的类上。为什么?因此你可以在适当的时候调用 Angular 提供的 onChange
和 onTouch
方法。我们将在下面看到它是如何工作的。
- d) 当我们实现它时,我们也将看到
writeValue
方法的工作原理。我把它放在这里,这样 ControlValueAccessor
上所有必需的属性都被实现了,你的代码仍然可以编译。
实现 writeValue
writeValue
的作用是:当外部表单控件发生变化时,在你的自定义组件内部执行某些操作。例如,如果你将自定义表单控件组件命名为 app-custom-input
,并在父组件中像这样使用它:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
当父组件以某种方式更改myFormControl
的值时,writeValue
将被触发。例如,在表单初始化期间(this.form = this.formBuilder.group({myFormControl: ""});
)或在表单重置this.form.reset();
期间。
如果表单控件的值在外部更改,则通常需要将其写入表示表单控件值的本地变量中。例如,如果您的CustomInputComponent
围绕基于文本的表单控件展开,它可能如下所示:
writeValue(input: string) {
this.input = input;
}
并且在 CustomInputComponent
的 html 中:
<input type="text"
[ngModel]="input">
你也可以按照Angular文档的描述将其直接写入输入元素。
现在,你已经处理了组件内部发生变化时外部的情况。现在让我们看看另一个方向。当你的组件内部发生变化时,如何通知外部世界?
调用onChange
下一步是通知父组件有关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
方法绑定到你的类上。
调用onTouch
由于表单控件可以被“触摸”,因此您还应该给Angular理解您的自定义表单控件何时被触摸的手段。你可以通过调用onTouch
函数来做到这一点。因此,在我们的例子中,如果您想保持符合Angular对开箱即用表单控件的处理方式,您应该在输入字段失去焦点时调用onTouch
。
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
再次强调,onTouch
是我选择的名称,但它的实际功能由Angular提供,并且不需要任何参数。这很有意义,因为你只需让Angular知道表单控件已被触摸。
将所有内容放在一起
那么当所有内容集成在一起时,它应该看起来像这样:
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'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
export class CustomInputComponent implements ControlValueAccessor {
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
input: string;
writeValue(input: string) {
this.input = input;
}
}
// 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
来源