在自定义窗体控件中具有无效值的写入值



当我想在 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和自定义表单控件的想法是:

  1. 为用户提供一种查看控件值的方法
  2. 提供 用户将值写入控件的一种方式

如果您在没有用户输入的情况下自行更改控制值,则与事物的范式背道而驰。

以下是我会怎么做:

  1. 如果值不正确,我会提供一个用于计算/显示的回退值,这样就不会有错误试图操作错误的值(无法读取 null/undefined 等的 X — 以防您需要它用于更复杂的数据类型)。

  2. 我会编写一个函数来检查控件中的值是否符合控制限制,如果不符合 — 在控制台中显示错误,也许是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 如何设置与FormControlsControlValueAccessors之间的绑定的说明。

您将从链接中看到,onChange回调只是调用formControl.setValue(),除非带有emitModelToViewChange: false标志。此标志在formControl.setValue()方法内部使用,当它为 false 时(在本例中如此),不会通知onChange的订阅者。

你可以设置一个模式验证器,然后当模式不匹配时,angular 就会知道该值无效。

const numberOnlyControl = new FormControl('', Validators.pattern('[0-9]+'))