Angular RangeError:使用valueChanges.subscribe时超出了最大调用堆栈大小

Angular RangeError:使用valueChanges.subscribe时超出了最大调用堆栈大小,angular,typescript,angular5,angular-reactive-forms,Angular,Typescript,Angular5,Angular Reactive Forms,我将Angular 5用于反应式表单,需要利用valueChanges动态禁用所需的验证 组件类别: export class UserEditor implements OnInit { public userForm: FormGroup; userName: FormControl; firstName: FormControl; lastName: FormControl; email: FormControl; loginTypeId:

我将Angular 5用于反应式表单,需要利用valueChanges动态禁用所需的验证

组件类别:

export class UserEditor implements OnInit {

    public userForm: FormGroup;
    userName: FormControl;
    firstName: FormControl;
    lastName: FormControl;
    email: FormControl;
    loginTypeId: FormControl;
    password: FormControl;
    confirmPassword: FormControl;
...

ngOnInit() {
    this.createFormControls();
    this.createForm();
    this.userForm.get('loginTypeId').valueChanges.subscribe(

            (loginTypeId: string) => {
                console.log("log this!");
                if (loginTypeId === "1") {
                    console.log("disable validators");
                    Validators.pattern('^[0-9]{5}(?:-[0-9]{4})?$')]);
                    this.userForm.get('password').setValidators([]);
                    this.userForm.get('confirmPassword').setValidators([]);

                } else if (loginTypeId === '2') {
                    console.log("enable validators");
                    this.userForm.get('password').setValidators([Validators.required, Validators.minLength(8)]);
                    this.userForm.get('confirmPassword').setValidators([Validators.required, Validators.minLength(8)]);

                }

                this.userForm.get('loginTypeId').updateValueAndValidity();

            }

        )
}
createFormControls() {
    this.userName = new FormControl('', [
        Validators.required,
        Validators.minLength(4)
    ]);
    this.firstName = new FormControl('', Validators.required);
    this.lastName = new FormControl('', Validators.required);
    this.email = new FormControl('', [
      Validators.required,
      Validators.pattern("[^ @]*@[^ @]*")
    ]);
    this.password = new FormControl('', [
       Validators.required,
       Validators.minLength(8)
    ]);
    this.confirmPassword = new FormControl('', [
        Validators.required,
        Validators.minLength(8)
    ]);

}

createForm() {
 this.userForm = new FormGroup({
      userName: this.userName,
      name: new FormGroup({
        firstName: this.firstName,
        lastName: this.lastName,
      }),
      email: this.email,
      loginTypeId: this.loginTypeId,
      password: this.password,
      confirmPassword: this.confirmPassword
    });
}
然而,当我运行它时,我得到一个浏览器javascript错误

UserEditor.html:82 ERROR RangeError: Maximum call stack size exceeded
    at SafeSubscriber.tryCatcher (tryCatch.js:9)
    at SafeSubscriber.webpackJsonp.../../../../rxjs/_esm5/Subscription.js.Subscription.unsubscribe (Subscription.js:68)
    at SafeSubscriber.webpackJsonp.../../../../rxjs/_esm5/Subscriber.js.Subscriber.unsubscribe (Subscriber.js:124)
    at SafeSubscriber.webpackJsonp.../../../../rxjs/_esm5/Subscriber.js.SafeSubscriber.__tryOrUnsub (Subscriber.js:242)
    at SafeSubscriber.webpackJsonp.../../../../rxjs/_esm5/Subscriber.js.SafeSubscriber.next (Subscriber.js:186)
    at Subscriber.webpackJsonp.../../../../rxjs/_esm5/Subscriber.js.Subscriber._next (Subscriber.js:127)
    at Subscriber.webpackJsonp.../../../../rxjs/_esm5/Subscriber.js.Subscriber.next (Subscriber.js:91)
    at EventEmitter.webpackJsonp.../../../../rxjs/_esm5/Subject.js.Subject.next (Subject.js:56)
    at EventEmitter.webpackJsonp.../../../core/esm5/core.js.EventEmitter.emit (core.js:4319)
    at FormControl.webpackJsonp.../../../forms/esm5/forms.js.AbstractControl.updateValueAndValidity (forms.js:3377)
“LogThis!”被反复调用,就像被递归调用一样,这就是为什么它们是堆栈错误

如果我删除了valueChanges.subscribe,那么除了有条件地删除验证之外,代码还可以工作


为什么要递归调用valueChanges.subscribe?

尝试在管道中添加
subscribe()
之前的
distinctUntilChanged()
。它应该过滤掉那些值没有实际更改的“更改”事件。

问题是,您修改了同一字段的
valueChanges
事件处理程序中的字段值,导致事件再次触发:

this.userForm.get('loginTypeId').valueChanges.subscribe(
  (loginTypeId: string) => {
    ...
    this.userForm.get('loginTypeId').updateValueAndValidity(); <-- Triggers valueChanges!
}
this.userForm.get('loginTypeId').valueChanges.subscribe(
(loginTypeId:string)=>{
...

this.userForm.get('loginTypeId').updateValueAndValidity();如果您想要订阅任何表单更改,并且仍然在其中运行patchValue,那么您可以将
{emitEvent:false}
选项添加到patchValue中,这样修补将不会触发另一个更改检测

代码:

另外,这也比逐个订阅每个表单控件要简单,以避免触发超出最大调用堆栈的更改。尤其是当您的表单有100个控件要订阅时

现在,为了进一步说明,如果您仍然需要在订阅中更新evalueandvality,那么我建议您使用
distinctUntilChanged
rxjs操作符,仅在某些值更改时运行订阅

可在此处找到更改后的文档

distinctUntilChanged-仅当当前值不同时发射 比上一次好

现在,我们还必须使其成为一个自定义的验证函数,因为默认情况下,distinctUntilChanged通过指针验证对象,并且每次更改时指针都是新的

this.formGroup
    .valueChanges
    .distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b))
    .subscribe( _ => {
        this.formGroup.get( 'controlName' ).patchValue( _val, {emitEvent: false} );
        this.formGroup.get( 'controlName' ).updateValueAndValidity();
    } );

瞧,我们正在修补和更新,没有遇到最大的调用堆栈!

我的答案只是开发

通过在
subscribe()
之前的管道中添加
distinctUntilChanged()
,可以避免“超过最大调用堆栈大小”,因为

distinctUntilChanged方法仅在当前值与上一个值不同时发出

用法:

this.userForm.get('password')
  .valueChanges.pipe(distinctUntilChanged())         
  .subscribe(val => {})

这不是因为您在事件处理程序的末尾调用了
updateValueAndValidity()
?我同意ConnorsFan的说法,
updateValueAndValidity()
可能会再次触发valueChanges,导致无限长的loop@ConnorsFan这就是递归的原因。我不应该更新我正在监视更改的同一字段。代码应该是“this.userForm.get('loginTypeId')”。您可以将其放入回答中。是的,谢谢我必须更改为this.userForm.get('password')).updateValueAndValidity();this.userForm.get('confirmPassword').updateValueAndValidity();
如果我在formgroup中使用forEach怎么办?这是一个通用的解决方案,在您对多个且相互依赖的
formControls
执行
值更改时也可以使用。谢谢!不知怎的,这对我没有帮助:(这只是意味着您的代码在某些方面有所不同。发布您自己的问题,有人会回答。我正在寻找的解决方案。比使用布尔值“valueChangesRunning”更好。+1如果我对formgroup使用forEach怎么办?
this.userForm.get('password')
  .valueChanges.pipe(distinctUntilChanged())         
  .subscribe(val => {})