writeValue 在自定义表单控件中具有无效值
writeValue with invalid value in custom form control
当我想在 Angular 中创建自定义表单控件时,我需要实现定义 writeValue(value:any): void
函数的 ControlValueAccessor
。
现在假设自定义组件由于其内部工作原理只接受仅包含数字的字符串。它不能接受任何其他东西,因为它不能用它做任何有意义的事情。
有人用无效值调用 formControl.setValue()
时的问题:
formControl
的值应该是多少(null
或输入无效)?
- 如果输入无效,
writeValue
是否应该使用 null
调用 onChange
回调,以便 Angular 知道该值未被接受?
我有类似的问题。我的情况是我试图为我们的 select 组件设置不存在的值。
我查看了原生 select.value
属性和 jQuery 的 val 函数如何处理这个问题:
结果是:el.value
returns''
和jEl.val()
returnsnull
。
(https://codepen.io/mino123456/pen/VRGwYx)
所以我认为值应该return 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
。如果 angular 能提供一些更好的方法,我会 喜欢 。
我对此事的看法是,如果来自控件的值无效,则不应更改 ControlValueAccessor
中的表单控件值。 ControlValueAccessor
和自定义表单控件的思路是:
- 为用户提供一种查看控件值的方法
- 提供
用户将值写入控件的一种方法
如果您在没有用户输入的情况下自行更改控件值,那将违反事物的范例。
我会这样做:
如果值不正确,我会提供一个后备值用于 calculations/displayed,这样在尝试操作错误值时就不会出现错误(无法读取 [= 的 X 51=] 等——如果您需要它来处理更复杂类型的数据)。
我会编写一个函数来检查控件中的值是否符合控件的限制,如果不符合 - 在控制台中显示错误,可能带有 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
发出您所写的内容,因此如果您有很多新项目并且更喜欢在后端过滤它们,您可以使用此输入从服务器请求新项目。
你可以设置一个pattern validator,那么angular会在模式不匹配的时候知道这个值是无效的
const numberOnlyControl = new FormControl('', Validators.pattern('[0-9]+'))
考虑到有问题的 ControlValueAccessor
组件 要求 它的值是一个具有您提到的条件的字符串,我看不出除了抛出异常之外还有什么在无效输入的情况下将是一个可接受的响应。
如果有人在您的 FormControl
上呼叫 formControl.SetValue()
并且他们的输入 无效 ,他们应该立即得到通知。将控件的值设置为 null 或其他方式只会隐藏 问题并可能在将来导致新问题(而且它们可能很难追踪)。
归根结底,唯一可以调用此方法的人是使用 FormControl
的开发人员,除非您正在创建某种 public API,在这种情况下,我建议提供强类型 'wrapper' 方法。
关于你的第二个问题:
Should writeValue call onChange callback with null in case of invalid input, so that Angular knows that the value was not accepted?
嗯,首先我会建议按照我的建议去做,在这种特殊情况下根本不要设置值。但作为旁注,您根本不应该在 writeValue
内部调用 onChange
回调。
WriteValue
是当您通过 formControl.SetValue()
以编程方式设置 FormControl
值 时 Angular 调用的方法。换句话说,它已经知道值已经改变,因为它是改变的原因。
当您通过 ControlValueAccessor
设置值时,您应该调用 onChange
回调,以便 Angular FormControl
可以知道此更改并更新其内在价值。这不应该发生在 writeValue
方法中,同样,Angular 将调用该方法,而不是你。
根据你问题的措辞,我怀疑你已经知道了。但是还有一个你不应该知道的原因:
在 writeValue
中调用 onChange
回调甚至不会产生任何影响,也不会如您所愿。 Here 解释了 Angular 表单如何设置绑定 to/from FormControls
和 ControlValueAccessors
.
您将从 link 中看到 onChange
回调只是调用 formControl.setValue()
,但带有 emitModelToViewChange: false
标志。此标志在 formControl.setValue()
方法内部使用,当它为 false 时(如本例所示),将不会通知 onChange
的订阅者。
当我想在 Angular 中创建自定义表单控件时,我需要实现定义 writeValue(value:any): void
函数的 ControlValueAccessor
。
现在假设自定义组件由于其内部工作原理只接受仅包含数字的字符串。它不能接受任何其他东西,因为它不能用它做任何有意义的事情。
有人用无效值调用 formControl.setValue()
时的问题:
formControl
的值应该是多少(null
或输入无效)?- 如果输入无效,
writeValue
是否应该使用null
调用onChange
回调,以便 Angular 知道该值未被接受?
我有类似的问题。我的情况是我试图为我们的 select 组件设置不存在的值。
我查看了原生 select.value
属性和 jQuery 的 val 函数如何处理这个问题:
结果是:el.value
returns''
和jEl.val()
returnsnull
。
(https://codepen.io/mino123456/pen/VRGwYx)
所以我认为值应该return 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
。如果 angular 能提供一些更好的方法,我会 喜欢 。
我对此事的看法是,如果来自控件的值无效,则不应更改 ControlValueAccessor
中的表单控件值。 ControlValueAccessor
和自定义表单控件的思路是:
- 为用户提供一种查看控件值的方法
- 提供 用户将值写入控件的一种方法
如果您在没有用户输入的情况下自行更改控件值,那将违反事物的范例。
我会这样做:
如果值不正确,我会提供一个后备值用于 calculations/displayed,这样在尝试操作错误值时就不会出现错误(无法读取 [= 的 X 51=] 等——如果您需要它来处理更复杂类型的数据)。
我会编写一个函数来检查控件中的值是否符合控件的限制,如果不符合 - 在控制台中显示错误,可能带有
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
发出您所写的内容,因此如果您有很多新项目并且更喜欢在后端过滤它们,您可以使用此输入从服务器请求新项目。
你可以设置一个pattern validator,那么angular会在模式不匹配的时候知道这个值是无效的
const numberOnlyControl = new FormControl('', Validators.pattern('[0-9]+'))
考虑到有问题的 ControlValueAccessor
组件 要求 它的值是一个具有您提到的条件的字符串,我看不出除了抛出异常之外还有什么在无效输入的情况下将是一个可接受的响应。
如果有人在您的 FormControl
上呼叫 formControl.SetValue()
并且他们的输入 无效 ,他们应该立即得到通知。将控件的值设置为 null 或其他方式只会隐藏 问题并可能在将来导致新问题(而且它们可能很难追踪)。
归根结底,唯一可以调用此方法的人是使用 FormControl
的开发人员,除非您正在创建某种 public API,在这种情况下,我建议提供强类型 'wrapper' 方法。
关于你的第二个问题:
Should writeValue call onChange callback with null in case of invalid input, so that Angular knows that the value was not accepted?
嗯,首先我会建议按照我的建议去做,在这种特殊情况下根本不要设置值。但作为旁注,您根本不应该在 writeValue
内部调用 onChange
回调。
WriteValue
是当您通过 formControl.SetValue()
以编程方式设置 FormControl
值 时 Angular 调用的方法。换句话说,它已经知道值已经改变,因为它是改变的原因。
当您通过 ControlValueAccessor
设置值时,您应该调用 onChange
回调,以便 Angular FormControl
可以知道此更改并更新其内在价值。这不应该发生在 writeValue
方法中,同样,Angular 将调用该方法,而不是你。
根据你问题的措辞,我怀疑你已经知道了。但是还有一个你不应该知道的原因:
在 writeValue
中调用 onChange
回调甚至不会产生任何影响,也不会如您所愿。 Here 解释了 Angular 表单如何设置绑定 to/from FormControls
和 ControlValueAccessors
.
您将从 link 中看到 onChange
回调只是调用 formControl.setValue()
,但带有 emitModelToViewChange: false
标志。此标志在 formControl.setValue()
方法内部使用,当它为 false 时(如本例所示),将不会通知 onChange
的订阅者。