当我想在 Angular 中创建自定义表单控件时,我需要实现定义writeValue(value:any): void
函数的ControlValueAccessor
。
现在假设自定义组件只接受仅包含数字的字符串,因为它的内部工作。它不能接受任何其他东西,因为它不能用它做任何有意义的事情。
当有人使用无效值调用formControl.setValue()
时的问题:
- 该
formControl
的值应该是多少(null
或无效输入)? writeValue
是否应该在输入无效的情况下使用null
调用onChange
回调,以便 Angular 知道该值未被接受?
我也有类似的问题。我的情况是我试图为我们的选择组件设置不存在的值。
我查看了本机select.value
属性和jQuery的val函数如何处理这个问题:
事实证明:el.value
返回''
,jEl.val()
返回null
. (https://codepen.io/mino123456/pen/VRGwYx)
所以我认为价值应该返回null
.
正如@Ludevik指出的,问题是在writeValue
中调用onChange
会将值设置为null
,但它也会触发valueChange
事件。这是不可取的,尤其是在以下情况下:
formControl.writeValue('invalidValue', {emitEvent:false});
我查看了源代码,找到了在不触发事件的情况下设置值的方法。 但我必须说,感觉真的被黑客入侵了。
ngOnInit(): any {
this._boundFormControl = this._injector.get(NgControl, null);
}
setControlValue(value: any): void {
const dir = this._boundFormControl as any;
const control = dir._parent.form.get(dir.path);
control.setValue(value, {
emitModelToViewChange: false,
emitEvent: false
});
}
基本思想是从父级获取formControl
,并用emitEvent: false
调用setValue
。如果角度能提供更好的方法,我会很高兴。
我对此事的想法是,如果来自控件的值无效,则不应更改ControlValueAccessor
中的表单控件值。ControlValueAccessor
和自定义表单控件的想法是:
- 为用户提供一种查看控件值的方法
- 提供 用户将值写入控件的一种方式
如果您在没有用户输入的情况下自行更改控制值,则与事物的范式背道而驰。
以下是我会怎么做:
-
如果值不正确,我会提供一个用于计算/显示的回退值,这样就不会有错误试图操作错误的值(无法读取 null/undefined 等的 X — 以防您需要它用于更复杂的数据类型)。
-
我会编写一个函数来检查控件中的值是否符合控制限制,如果不符合 — 在控制台中显示错误,也许是
console.assert
,因为这有点像破坏自定义控件的 API 合约。
简而言之,这是我对组合框的实现(当然省略了很多并简化了它):
@Input()
items: ReadonlyArray<T>;
@Input()
stringify = (item: T) => !!item
? item.toString()
: ''; // <- or any other conversion to string
@Input()
search: string
@Output()
searchChange = new EventEmitter<string>();
onNestedModelChange(search: string) {
this.updateSearch(search);
}
onSelect(item: T) {
this.onChange(item);
this.updateSearch(this.stringify(item));
}
writeValue(value: T) {
this.updateSearch(this.stringify(value));
}
private updateSearch(search: string) {
this.search = search;
this.searchChange.emit(search);
}
模板:
<input [ngModel]="search" (ngModelChange)="onNestedModelChange($event)">
<div class="dropdown">
<div *ngFor="let item of items | filterPipe: search"
class="option"
(click)="onSelect(item)">
{{item}}
</div>
</div>
用法:
<combobox
[formControl]="control"
[items]="items$ | async"
(searchChange)="queryNewItems($event)"
></combobox>
这样,您就有了一个单独的字符串输入search
,当您写入该输入时,您可以使用该输入来填充嵌套的input
标签 — 您可以使用一些过滤管道(例如stringify(item).indexOf(search)
)过滤所有可用项目。您还可以发出通过searchChange
编写的内容,因此,如果您有很多新项目并且更喜欢在后端过滤它们,则可以使用此输入从服务器请求新项目。
鉴于有问题的ControlValueAccessor
组件要求其值是具有您提到的条件的字符串,我看不出在无效输入的情况下,除了抛出异常之外的任何事情都是可接受的响应。
如果有人在您的FormControl
上呼叫formControl.SetValue()
并且他们的输入无效,则应立即通知他们。将控件的值设置为 null 或以其他方式只会隐藏问题,并可能在将来导致新问题(并且可能很难跟踪)。
归根结底,唯一可以调用此方法的人是使用FormControl
的开发人员,除非您正在创建某种公共 API,在这种情况下,我建议提供强类型的"包装器"方法。
至于你的第二个问题:
在输入无效的情况下,writeValue 是否应该使用 null 调用 onChange 回调,以便 Angular 知道该值未被接受?
好吧,首先,我建议遵循我的建议,在这种特殊情况下根本不设置值。但作为旁注,您根本不应该在writeValue
内部调用onChange
回调。
WriteValue
是以编程方式通过formControl.SetValue()
设置FormControl
值时 Angular 调用的方法。换句话说,它已经知道值已经改变,因为它是这种变化的原因。
当您通过ControlValueAccessor
设置值时,您应该调用onChange
回调,以便 AngularFormControl
可以了解此更改并更新其内部值。不过,这不应该发生在writeValue
方法中,因为同样,它是 Angular 将调用的方法,而不是您。
根据你问题的措辞,我怀疑你已经知道这一点。但是,您不应该这样做的另一个原因可能没有意识到:
在writeValue
内部调用onChange
回调甚至不会有什么不同,也不会像你认为的那样。以下是 Angular Forms 如何设置与FormControls
和ControlValueAccessors
之间的绑定的说明。
您将从链接中看到,onChange
回调只是调用formControl.setValue()
,除非带有emitModelToViewChange: false
标志。此标志在formControl.setValue()
方法内部使用,当它为 false 时(在本例中如此),不会通知onChange
的订阅者。
你可以设置一个模式验证器,然后当模式不匹配时,angular 就会知道该值无效。
const numberOnlyControl = new FormControl('', Validators.pattern('[0-9]+'))