Angular 4 ControlValueAccessor 值提交和值更改事件
Angular 4 ControlValueAccessor value on submit and value changes events
在我的 Angular (4 + Typescript) 项目中,我使用了已经准备好的组件(这并不重要,因为扩展 ControlValueAccessor 的方式是相同的,但如果这很重要,我使用了 ng2-select) 我想要它是因为更好的用户体验和更好看的外观。将它包含到我看到的使用反应式表单的表单组件后,在提交时我返回(用于表单控件)基于准备组件内部使用的 class SelectItem 的对象,以管理 select编辑项目 - 像这样:
// console.log(this.myForm.values)
Object {
txtInput: 'value',
// ...
preparedComponent: SelectItem { // <--
id: '1',
text: 'Chosen item'
}
}
因为该组件基本上只是标准 select 控件的替代品,所以我期望我会取回以字符串形式形成的所选项目的 ID - 如下所示:
// console.log(this.myForm.values)
Object {
txtInput: 'value',
// ...
preparedComponent: '1' // <--
}
在了解了 ControlValueAccessor 的实际含义及其工作原理后,我将提交时返回的内容(或者更确切地说是项目更改 - onChange
)更改为项目 ID。问题解决了一段时间,直到我开始设置预定义值(在编辑实例上)以形成控件而不是空值(在添加实例上)。我发现这次 writeValue
正在根据作为预定义值发送给它的内容来分配值以形成控件。因此,如果我将预定义值发送为 ...
Object {
id: '1',
text: 'Chosen item'
}
...这也是 Object on submit 中的值,就像上面的第一个代码示例一样。我为此做了一个解决方法,在调用 writeValue
之后立即使用 onChange
分配正确的值来控制。这确实是不正确的做法,但我想不出除此之外的任何事情:
public writeValue(val: any): void {
this.selectedItems = val; // val is for example Object { id: '1', text: 'Chosen item' }
setTimeout(() => {
this.onChange(val.id); // just an example of emitted value, there should be some checks val is object and id exists in real code
}, 0);
}
就我没有开始订阅表单控件 valueChanges
而言,这也有效。问题是,每次为准备好的组件分配预定义值时,即使在使用 { emitEvent: false }
.
时,也会发出提交后使用的值,返回给订阅者
所以这就是我想用准备好的组件以某种方式处理的事情,因为我想在提交时从控件中获得正确的值,并且我不想在值真的没有改变之前向订阅者发出更改,而不是只是替换为预定义值。任何想法如何处理这将不胜感激。
我不确定我是否理解正确,但我想试一试。
您希望组件的输入为以下类型:
{
id: string,
text: string
}
但您希望组件的 输出 只是 selected 项目的字符串 ID。
我认为这个数据流有点尴尬。在初始化期间,preparedComponent
是一个 object
,但是当一个项目被 select 编辑时,你想把它变成一个 string
。撇开类型安全的问题不谈,就是感觉不直观。
为了比较,想象一下使用 ngModel
和文本输入:
<input type="text" [(ngModel)]="preparedComponent" />
您是否希望传入一个对象,并在用户键入时返回一个字符串?应该不是。
但是让我们假设由于某种原因它一定是这样的。您通过修改 ng2-select
对 ControlValueAccessor
的实现或创建您自己的实现 ControlValueAccessor
并包装 ng2-select
的组件来继续。 (我不知道你在你的问题中做了哪些)。现在,当一个项目被 selected 时调用 this.onChange(val.id)
,这会使用字符串 ID 更新父组件。
然后您注意到一个单独的问题,即如果您预定义了一个 selected 值,ng2-select
会在初始化期间触发一个更改事件。这很奇怪,因为用户还没有与 select 控件交互。因此,您尝试通过使用 emitEvent: false
在表单控件上调用 setValue
来初始化 preparedComponent
,但我猜这没有用,因为虽然它可能阻止了更改事件在您初始化了您的数据,ng2-select
立即对您的数据进行了第二次更改。
因此,您通过等到 ng2-select
的第一次更新将父组件更新为新对象来解决此问题,然后(使用 setTimeout
)用字符串 ID 覆盖它你真的想要。这使 preparedComponent
与字符串保持同步,但无法解决更改事件触发的问题,即使用户未与控件交互也是如此。 (顺便说一下,如果你用自己的组件包装 ng2-select
那么我不确定你为什么必须做这个 setTimeout
部分,因为你不会已经有另一个函数调用this.onChange(val.id)
当 ng2-select
通知您一个新的 selected 项目时,正如我在上一段中提到的那样?)
因为我无法判断你是直接修改ng2-select
,还是用你自己的组件包装它:
如果您正在修改 ng2-select
,为什么不直接找到它的 writeValue
方法并删除发出事件的代码?这样它会在您预定义值时更新 DOM,但不会覆盖您的值。
如果您用自己的组件包装 ng2-select
,为什么不直接修改 writeValue
方法以在 ng2-select
即将触发事件时存储一个标志,这样您就可以忽略它:
private: ignoreSelectedEvent = false;
public writeValue(val: any): void {
this.ignoreSelectedEvent = true;
// Update ng2-select, which will cause an event to fire
}
然后在您对新 selected 项目做出反应的函数中:
if (this.ignoreSelectedEvent) {
this.ignoreSelectedEvent = false;
return;
}
// Update the model with the string ID
this.onChange(val.id);
基本上问题是询问是否可以将不同的值类型作为初始值发送到控件组件,由 writeValue 方法处理。然后,这个应该将值转换为字符串 ID 或字符串 ID 数组,具体取决于选择的类型 - 单个值或多个值。正如弗兰克在他的回答中所说,这样做并使用 setTimeout 解决方法调用 onChange 有点尴尬。
所以解决方案是在将某些对象转换为ID/array ID 后,将初始值设置为onChange 中控制组件return 的值。组件本身必须根据发送到其中的 ID/IDs 从所有可用项目中选择合适的对象。
在我的 Angular (4 + Typescript) 项目中,我使用了已经准备好的组件(这并不重要,因为扩展 ControlValueAccessor 的方式是相同的,但如果这很重要,我使用了 ng2-select) 我想要它是因为更好的用户体验和更好看的外观。将它包含到我看到的使用反应式表单的表单组件后,在提交时我返回(用于表单控件)基于准备组件内部使用的 class SelectItem 的对象,以管理 select编辑项目 - 像这样:
// console.log(this.myForm.values)
Object {
txtInput: 'value',
// ...
preparedComponent: SelectItem { // <--
id: '1',
text: 'Chosen item'
}
}
因为该组件基本上只是标准 select 控件的替代品,所以我期望我会取回以字符串形式形成的所选项目的 ID - 如下所示:
// console.log(this.myForm.values)
Object {
txtInput: 'value',
// ...
preparedComponent: '1' // <--
}
在了解了 ControlValueAccessor 的实际含义及其工作原理后,我将提交时返回的内容(或者更确切地说是项目更改 - onChange
)更改为项目 ID。问题解决了一段时间,直到我开始设置预定义值(在编辑实例上)以形成控件而不是空值(在添加实例上)。我发现这次 writeValue
正在根据作为预定义值发送给它的内容来分配值以形成控件。因此,如果我将预定义值发送为 ...
Object {
id: '1',
text: 'Chosen item'
}
...这也是 Object on submit 中的值,就像上面的第一个代码示例一样。我为此做了一个解决方法,在调用 writeValue
之后立即使用 onChange
分配正确的值来控制。这确实是不正确的做法,但我想不出除此之外的任何事情:
public writeValue(val: any): void {
this.selectedItems = val; // val is for example Object { id: '1', text: 'Chosen item' }
setTimeout(() => {
this.onChange(val.id); // just an example of emitted value, there should be some checks val is object and id exists in real code
}, 0);
}
就我没有开始订阅表单控件 valueChanges
而言,这也有效。问题是,每次为准备好的组件分配预定义值时,即使在使用 { emitEvent: false }
.
所以这就是我想用准备好的组件以某种方式处理的事情,因为我想在提交时从控件中获得正确的值,并且我不想在值真的没有改变之前向订阅者发出更改,而不是只是替换为预定义值。任何想法如何处理这将不胜感激。
我不确定我是否理解正确,但我想试一试。
您希望组件的输入为以下类型:
{
id: string,
text: string
}
但您希望组件的 输出 只是 selected 项目的字符串 ID。
我认为这个数据流有点尴尬。在初始化期间,preparedComponent
是一个 object
,但是当一个项目被 select 编辑时,你想把它变成一个 string
。撇开类型安全的问题不谈,就是感觉不直观。
为了比较,想象一下使用 ngModel
和文本输入:
<input type="text" [(ngModel)]="preparedComponent" />
您是否希望传入一个对象,并在用户键入时返回一个字符串?应该不是。
但是让我们假设由于某种原因它一定是这样的。您通过修改 ng2-select
对 ControlValueAccessor
的实现或创建您自己的实现 ControlValueAccessor
并包装 ng2-select
的组件来继续。 (我不知道你在你的问题中做了哪些)。现在,当一个项目被 selected 时调用 this.onChange(val.id)
,这会使用字符串 ID 更新父组件。
然后您注意到一个单独的问题,即如果您预定义了一个 selected 值,ng2-select
会在初始化期间触发一个更改事件。这很奇怪,因为用户还没有与 select 控件交互。因此,您尝试通过使用 emitEvent: false
在表单控件上调用 setValue
来初始化 preparedComponent
,但我猜这没有用,因为虽然它可能阻止了更改事件在您初始化了您的数据,ng2-select
立即对您的数据进行了第二次更改。
因此,您通过等到 ng2-select
的第一次更新将父组件更新为新对象来解决此问题,然后(使用 setTimeout
)用字符串 ID 覆盖它你真的想要。这使 preparedComponent
与字符串保持同步,但无法解决更改事件触发的问题,即使用户未与控件交互也是如此。 (顺便说一下,如果你用自己的组件包装 ng2-select
那么我不确定你为什么必须做这个 setTimeout
部分,因为你不会已经有另一个函数调用this.onChange(val.id)
当 ng2-select
通知您一个新的 selected 项目时,正如我在上一段中提到的那样?)
因为我无法判断你是直接修改ng2-select
,还是用你自己的组件包装它:
如果您正在修改 ng2-select
,为什么不直接找到它的 writeValue
方法并删除发出事件的代码?这样它会在您预定义值时更新 DOM,但不会覆盖您的值。
如果您用自己的组件包装 ng2-select
,为什么不直接修改 writeValue
方法以在 ng2-select
即将触发事件时存储一个标志,这样您就可以忽略它:
private: ignoreSelectedEvent = false;
public writeValue(val: any): void {
this.ignoreSelectedEvent = true;
// Update ng2-select, which will cause an event to fire
}
然后在您对新 selected 项目做出反应的函数中:
if (this.ignoreSelectedEvent) {
this.ignoreSelectedEvent = false;
return;
}
// Update the model with the string ID
this.onChange(val.id);
基本上问题是询问是否可以将不同的值类型作为初始值发送到控件组件,由 writeValue 方法处理。然后,这个应该将值转换为字符串 ID 或字符串 ID 数组,具体取决于选择的类型 - 单个值或多个值。正如弗兰克在他的回答中所说,这样做并使用 setTimeout 解决方法调用 onChange 有点尴尬。
所以解决方案是在将某些对象转换为ID/array ID 后,将初始值设置为onChange 中控制组件return 的值。组件本身必须根据发送到其中的 ID/IDs 从所有可用项目中选择合适的对象。