表单控件的valueChanges返回的是先前的值

114

我在一个名为'question1'的表单控件内,该控件位于parentForm表单对象中,我以以下方式订阅它。

它是一个具有两个选项YesNo的单选按钮,当我选择No时,它显示Yes,当我选择Yes时,它显示No

this.parentForm.controls['question1'].valueChanges.subscribe(
  (selectedValue) => {
    // If option `No is selected`
    console.log(selectedValue);  // displays No. OK
    console.log(this.parentForm.value['question1']);  // displays Yes. Problem is here
  }
);

selectedValue变量的值是正确的,但如果我使用console.log(this.parentForm.value['question1'],它会给出先前的值。

我尝试在从this.parentForm.value['question1']检索值之前放置一个setTimeout(),这样就可以正常工作了。

setTimeout(() => {
  console.log(this.parentForm.value['question1']); // gives the correct value.
}, 500);

但我的问题是,为什么parentForm在其控件的值更改时没有更新,而且我是在值更改后才检索其值。

注意:我不想观察parentForm.valueChanges,这不是我的要求。


那么,是什么阻止了您使用 selectedValue - Harry Ninh
使用 this.parentForm.value['question1'] 可以在组件的任何位置访问它。selectedValue 仅具有局部作用域。 - Amit Chigadani
5个回答

229

valueChanges 是一个 Observable,因此您可以使用管道 pairwise 在订阅中获取先前值和下一个值。

// No initial value. Will emit only after second character entered
this.form.get('fieldName')
  .valueChanges
  .pipe(pairwise())
  .subscribe(([prev, next]: [any, any]) => ... );
// Fill buffer with initial value, and it will emit immediately on value change
this.form.get('fieldName')
  .valueChanges
  .pipe(startWith(null), pairwise())
  .subscribe(([prev, next]: [any, any]) => ... );

在 StackBlitz 中展示了它的工作示例:https://stackblitz.com/edit/angular-reactive-forms-vhtxua

更新

如果你注意到 startWith 看起来已经被弃用,那是不正确的。该操作符仅有一个活动签名,您可以在这个答案中了解更多信息。

很有可能,您正在使用 startWith(null)startWith(undefined),尽管有警告,但它们并未被弃用。IDE 检测到错误的函数签名,并显示警告。

一个简单的解决方法是提供预期的返回类型:

// Prevent deprecation notice when using `startWith` since it has not been deprecated
this.form.get('fieldName')
  .valueChanges
  .pipe(startWith(null as string), pairwise())
  .subscribe(([prev, next]: [any, any]) => ... );

在 StackBlitz 中使用 startWith 的示例: https://stackblitz.com/edit/angular-reactive-forms-rvxiua


7
应该选择逐对比较。 - Rex
2
我已更新答案,以防止误解startWith已被弃用,并提供了一个来自StackOverflow的答案链接和引用,使得这个答案保持主题相关而不冗余。 - mtpultz
1
我们需要取消订阅这个订阅吗? - Chaka15
1
请注意,pairwise -> 会在源 Observable 的第二个及后续发射上发出值,但不会在第一次发射时发出值,因为在这种情况下没有先前的值。我认为这很遗憾,可以用未定义或空值的先前值来替代。 - Youp Bernoulli
1
@BernoulliIT 我认为你会发现当你使用 startWith(null) 时,示例二已经考虑到了这一点,如果你查看 StackBlitz 的示例二,你可以看到它的工作原理 - https://stackblitz.com/edit/angular-reactive-forms-vhtxua?file=app%2Fapp.component.ts - mtpultz
显示剩余12条评论

70

valueChanges 事件在新值更新到 FormControl 值之后被触发,但在更改被冒泡到它的父级和祖先之前。因此,您需要访问 FormControl 自身的值(刚刚被修补过),而不是 FormGroup 值对象的字段(在事件期间未触及)。

鉴于此,请使用 this.parentForm.get('question1').value 来代替:

this.parentForm.controls['question1'].valueChanges.subscribe(
    (selectedValue) => {
      console.log(selectedValue);
      console.log(this.parentForm.get('question1').value);     
    }
);

谢谢!这个完美地运作了。你有什么想法为什么另一种方法不起作用吗? - Amit Chigadani
@AmitChigadani,我已经更新了我的答案并加入了一些解释,请看一下。 - Harry Ninh
19
这里描述的功能被标记为错误:https://github.com/angular/angular/issues/13129 - corykon
6
RxJS操作符可以管理订阅结果并提取上一个和下一个值,而无需使用表单控件的值:https://dev59.com/UlcP5IYBdhLWcg3wFmgw#54205373 - mtpultz
不再工作了... - yehonatan yehezkel

7

尝试做这个

this.parentForm.controls['question1'].valueChanges.subscribe(
    (selectedValue) => {
      console.log(selectedValue);
      console.log(this.parentForm.value.question1);     
    }
);

如果FormBuilder的控件已更新,则可以通过value属性立即从FormBuilder对象中恢复上次的值。

2
这与被接受的答案非常相似。 - Michael
这不应该被视为一种依赖,因为它是利用Angular变化传播的方式进行的。请使用RxJS的pairwise操作符。 - refaelio

2

还有一种选择,但可能不适用于所有情况:您可以在FormControl更新selectedValue之前等待一个tick

setTimeout( () => console.log( selectedValue ) );

关于同步/异步表单的更多信息,请参见Angular Guide文档中的响应式表单


3
如果不是真正需要,就不要使用setTimeout,而在这里它并不是真正需要的。 - Ahmed Hasn.
1
这会增加不必要的复杂性并可能导致不良副作用。不要走这条路。 - Youp Bernoulli

0

不要使用:

 this.parentForm.controls['question1'].valueChanges.subscribe

使用:

this.parentForm.get('question1').valueChanges.subscribe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接