自定义窗体控件中具有无效值的writeValue

时间:2019-03-12 12:03:45

标签: angular angular-reactive-forms

当我想在Angular中创建自定义表单控件时,我需要实现定义ControlValueAccessor函数的writeValue(value:any): void

现在想象一下,由于内部组件的作用,自定义组件仅接受仅包含数字的字符串。它不能接受其他任何东西,因为它不能对它做任何有意义的事情。

有人用无效值呼叫formControl.setValue()时遇到的问题:

  • formControlnull或无效输入)的值应该是什么?
  • 在无效输入的情况下,writeValue是否应该使用onChange调用null回调,以便Angular知道该值未被接受?

4 个答案:

答案 0 :(得分:1)

我有类似的问题。在我的情况下,我试图为我们的选择组件设置不存在的值。

我看着原生select.value属性和jQuery的val函数如何解决这个问题:

事实证明:el.value返回'',而jEl.val()返回null。 (https://codepen.io/mino123456/pen/VRGwYx

所以我认为值应该返回null

正如@Ludevik指出的那样,问题在于在onChange中调用writeValue会将值设置为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,然后用setValue来调用emitEvent: false。如果角度会提供更好的方法,我会

答案 1 :(得分:1)

我对此事的想法是,如果来自控制的值无效,则不应更改ControlValueAccessor中的表单控制值。 ControlValueAccessor和自定义表单控件的想法是:

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

如果您在没有用户输入的情况下自行更改控制值,则会违反事物的范例。

这是我要怎么做:

  1. 如果值不正确,我将提供一个后备值以用于计算/显示,这样就不会出现尝试操作错误值的错误(无法读取X为null / undefined的情况,以防万一。您将需要使用它来处理更复杂的数据类型。

  2. 我将编写一个函数来检查控件中的值是否符合控件的限制,如果不兼容,请在控制台中显示错误,也许使用console.assert,因为这有点像破坏了API您自定义控件的合同。

这是我对combobox的简要介绍(当然,省略了很多并简化了):

@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标签,当您对该输入进行写操作时,可以使用一些过滤管道(例如{{1 }})。您还会发出通过stringify(item).indexOf(search)编写的内容,因此,如果有很多新项,并且希望在后端过滤它们,则可以使用此输入从服务器请求新项。

答案 2 :(得分:0)

您可以设置pattern validator,然后当模式不匹配时,angular就会知道该值无效。

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

答案 3 :(得分:0)

鉴于有问题的ControlValueAccessor组件要求,该组件的值必须是具有您提到的条件的字符串,所以我看不到除了引发异常之外的其他任何方式都可以被接受在输入无效的情况下。

如果有人在您的formControl.SetValue()上致电FormControl,并且他们的输入无效,则应立即通知他们。将控件的值设置为null或否则将仅隐藏问题,并可能在将来引起新问题(并且可能很难跟踪)。

最后,只有FormControl的开发人员才能调用此方法,除非您要创建某种公共API,在这种情况下,我建议您提供一种类型强的“包装器”方法。

关于第二个问题:

  

如果输入无效,writeValue是否应使用null调用onChange回调,以便Angular知道该值未被接受?

好吧,首先,我建议您按照我的建议进行操作,在这种特殊情况下,请不要设置任何值。但是,请注意,您根本不应该在onChange内部调用writeValue回调。

WriteValue是当您通过FormControl 以编程方式设置formControl.SetValue()时Angular调用的方法。换句话说,它已经知道值已更改,因为它是导致更改的原因。

通过onChange设置值时,应该调用ControlValueAccessor回调,以便Angular FormControl可以知道这一变化并更新其内部值。不过,writeValue方法中不应发生这种情况,同样,它也是Angular会调用的方法,而不是您。

根据您提出的问题,我怀疑您已经知道了。但是还有另一个原因使您不应该意识到:

onChange内调用writeValue回调甚至不会有所不同,也不会做您认为会做的事情。 Here是有关Angular Forms如何设置与FormControlsControlValueAccessors之间的绑定的说明。

您会从链接中看到onChange回调仅调用formControl.setValue(),除了带有emitModelToViewChange: false标志之外。该标志在formControl.setValue()方法内部使用,当它为false时(在这种情况下),将不会通知onChange的订户。