为啥在 Angular 中实现 ControlValueAccessor 时需要在 writeValue 中调用 onChange 和 onTouch?

Posted

技术标签:

【中文标题】为啥在 Angular 中实现 ControlValueAccessor 时需要在 writeValue 中调用 onChange 和 onTouch?【英文标题】:Why do I need call onChange and onTouch in writeValue when implementing ControlValueAccessor in Angular?为什么在 Angular 中实现 ControlValueAccessor 时需要在 writeValue 中调用 onChange 和 onTouch? 【发布时间】:2018-03-26 09:00:20 【问题描述】:

我已经实现了以下组件。它按预期工作和表现。尽管如此,由于ControlValueAccessor 的实现对我来说是新的,我不得不follow a blog 而不了解一些部分的更深层次的细节。所以这是一种“它有效但为什么?!”的情况。

@Component( selector: ..., templateUrl: ..., styleUrls: ...,
  providers: [ provide: NG_VALUE_ACCESSOR, 
                useExisting: forwardRef(() => InputTextComponent), 
                multi: true ]
)
export class InputComponent implements ControlValueAccessor 

  constructor()  
  @Input() info: string;
  onChange: any = () =>  
  onTouch: any = () =>  

  writeValue(input: any): void 
    this.info.value = input;
    // this.onChange(input);
    // this.onTouch();
  

  registerOnChange(_: any): void  this.onChange = _; 
  registerOnTouched(_: any): void  this.onTouch = _; 

  setDisabledState?(state: boolean): void  

  onEdit(value: string): void  this.onChange(value); 

当我让它工作时,我注释掉了writeValue(...) 方法的第二行和第三行,据我所知,没有任何问题。 other blogs 也一直建议这些调用,所以我相信忽略它们是不合适的。不过,我不相信魔法,更喜欢有具体的做事理由。

为什么在writeValue(...) 中执行对onChange(...)onTouch(...) 的调用很重要?会出现什么问题以及在什么情况下会出现问题?

作为一个支线任务,我还尝试注释掉 the other methods 并发现当我删除 setDisabledState(...) 时我无法说出任何事情。什么时候可以预期会引起问题?它真的需要实现吗(我见过括号前后都有问号的版本,参数如下:setDisabledState?(state: boolean): void ,但也像这样:setDisabledState(state: boolean)?: void )。

【问题讨论】:

你是如何使用这个组件的? 你读过angular.io/api/forms/ControlValueAccessor吗? Why is it important to execute the call to onChange(...) and onTouch(...) in writeValue(...)? 你在哪里看到的? @yurzui,这里大概指的是these @AngularInDepth.com 我想他指的是这个take.ms/IGcPJ 【参考方案1】:

阅读这篇文章,详细解释ControlValueAccessor

Never again be confused when implementing ControlValueAccessor in Angular forms

如果组件应该用作 Angular 表单的一部分,您通常需要在组件上实现 ControlValueAcceessor 接口。

我注释掉了 writeValue(...) 方法的第二行和第三行 而且,据我所知,没有任何损坏。

这可能是因为您没有应用任何表单指令 - formControlngModelFormControl 链接到您的自定义输入组件。 FormControl使用InputComponentwriteValue方法进行通信。

这是我上面引用的文章中的图片:

formControl 使用writeValue 方法为原生表单控件设置值。 registerOnChange 方法由formControl 使用来注册一个回调,该回调预计将在每次更新本机表单控件时触发。 registerOnTouched 方法用于指示用户与控件交互。

为什么执行对 onChange(...) 的调用很重要? 在 writeValue(...) 中的 onTouch(...)?什么会出错,在什么情况下 情况可以预期吗?

这是实现 ControlValueAcceessor 的自定义控件通知 Angular 的 FormControl 输入中的值已更改或用户与控件交互的机制。

...发现我什么都说不出来 去掉了 setDisabledState(...)...真的需要实现吗?

正如接口中指定的,当控件状态更改为“禁用”或从“禁用”更改时,表单 API 调用此函数。根据值,它应该启用或禁用适当的 DOM 元素。 如果您想在关联的FormControl 的状态变为disabled 时收到通知,则需要实现它,然后您可以执行一些自定义逻辑(例如,禁用您的输入组件)。

【讨论】:

很好的答案。不过有两个后续。 #1 如果我注册了onChange(),但不从writeValue() 调用它(仅将值设置为支持字段但不将其传播到onChange()),我仍然将值传递给父组件中的表单组.那么现在工作的是什么?我看不到它。你有一个明确的例子说明什么是不可行的吗? #2 我看到setDisableState() 没有问号,方法名称后面有问号,括号后面有问号。是同一个意思吗?这是怎么回事? @DonkeyBanana,请阅读我引用的文章,如果之后您仍有疑问,请创建一个 stackblitz 演示,让我知道文章中不清楚的地方 如果用户与您的 DOM 控件交互并更改其值,您必须调用 onChange,以便将值写入表单值。在writeValue 中调用它是错误的。如果程序更改表单控件的值,表单将调用此函数。您只需更新您的 DOM 控件以反映当前的表单值。 我错过了一个明确的声明,在writeValue 中调用onChange 是错误的,因为必须使用onChange 来通知FormControl 值的更改,并且使用writeValue更新用户界面。结合这些功能是一种循环依赖。您的回答一切正常。几天前我了解了 ControlValueAccessor 并偶然发现了 DonkeyBanana 引用的实现。所以我觉得有必要评论一下。 你让我摆脱了过去 3 天的极度痛苦。文章提供了深入的解释。谢谢!!【参考方案2】:

我不认为接受的答案以简洁的方式回答了最核心的问题:

为什么在 writeValue(...) 中调用 onChange(...) 和 onTouch(...) 很重要?

不是。您确实不需要在 writeValue 中调用 onChange。这不是预期用途(请参阅下面的文档链接)。

会出现什么问题以及在什么情况下会出现这种情况?

预计不会出错。详细回答:

如果您writeValue 内部调用onChange,您会注意到:

第一次 onChange 调用什么也不做。这是因为第一次调用 writeValue 时,onChange 回调尚未注册(即registerOnChange 未调用)。 稍后从 writeValue 内部调用 onChange 会使用您从模型中获得的值更新您的模型(使用 emitModelToViewChange = false 以避免递归调用 writeValue)。

换句话说,除非您依赖您的组件以某种方式立即更改它在 writeValue 中接收到的值,并将这些更改传递回您的模型,但second 以及稍后调用 writeValue,您可以安全地不从 writeValue 调用 onChange。

在此处查看文档中的示例:https://angular.io/api/forms/ControlValueAccessor

【讨论】:

有趣的事实 - OP 提供的示例链接现在实际上在 writeValue() 中都没有 this.onChange() 确实很有趣 - 真的是互联网课程更正了吗? @corolla 我想根据不同的标准转换我传递给我的 formControl 的值。所以我需要调用WriteValue()中的onChange()方法。既然 registerOnChange() 在 writeValue() 之后触发,我该怎么做?

以上是关于为啥在 Angular 中实现 ControlValueAccessor 时需要在 writeValue 中调用 onChange 和 onTouch?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 nrwl/nx 比 angular@6 vanilia 更好?

如何在 Angular 8 中实现 ngInit?

在 Angular 2 中实现滑动导航的最佳方式是啥?

在`Angular`中实现`Refresh Token`

如何在 Angular 6 中实现 Keycloak?

如何在没有 Angular 的 Cordova 中实现 Auth0 SSO