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

Posted

技术标签:

【中文标题】RangeError:使用 valueChanges.subscribe 时超出了最大调用堆栈大小【英文标题】:RangeError: Maximum call stack size exceeded when using valueChanges.subscribe 【发布时间】:2018-05-29 01:00:34 【问题描述】:

我正在使用带有反应式表单的 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)

“记录这个!”像递归调用一样被重复记录调用,这就是为什么它们是堆栈错误

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

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

【问题讨论】:

不是因为你在事件处理函数的最后调用了updateValueAndValidity()吗? 我同意 ConnorsFan 的观点,updateValueAndValidity() 可能会导致 valueChanges 再次触发,从而导致无限循环 @ConnorsFan 这就是递归的原因。我不应该更新我正在监视更改的同一字段。代码应该是 'this.userForm.get('loginTypeId').'。你可以把它放在答案中 【参考方案1】:

如果您想订阅任何表单更改并仍然在其中运行patchValue,那么您可以将emitEvent: false 选项添加到patchValue,因此修补不会触发另一个更改检测

代码:

this.formGroup
    .valueChanges
    .subscribe( _ => 
        this.formGroup.get( 'controlName' ).patchValue( _val, emitEvent: false );
     );

PS。这也比逐个订阅每个表单控件以避免触发更改最大调用堆栈超出。特别是如果您的表单有 100 个要订阅的控件。

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

distinctUntilChanged 文档可以在这里找到

https://www.learnrxjs.io/operators/filtering/distinctuntilchanged.html

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();
     );

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

【讨论】:

我正在寻找的解决方案。比拥有一个布尔值“valueChangesRunning”更好。 +1【参考方案2】:

我的回答只是this one的发展。

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

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

用法:

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

Documentation

【讨论】:

如果我在表单组中使用 forEach 会怎样? ***.com/questions/63303903/…【参考方案3】:

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

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

【讨论】:

是的,谢谢,我必须改成this.userForm.get('password').updateValueAndValidity();this.userForm.get('confirmPassword').updateValueAndValidity(); 如果我在表单组中使用 forEach 会怎样? ***.com/questions/63303903/…【参考方案4】:

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

【讨论】:

这是一个通用的解决方案,当您对多个 valueChanges 执行多个且相互依赖的 formControls 时,它也适用。谢谢! 不知何故对我没有帮助:( 这只是意味着您的代码在某些方面有所不同。发表您自己的问题,有人会回答。【参考方案5】:

我在进行验证时遇到了类似的错误。调用 updateValueAndValidity() 时引发错误。在我的情况下,我使用了这个重载 updateValueAndValidity(emitEvent : false)

试试这个

this.userForm.get('loginTypeId').updateValueAndValidity(emitEvent : false);

【讨论】:

【参考方案6】:
this.userForm.get('loginTypeId').enable(emitEvent: false);

如果您需要在所有表单中启用一项选择

【讨论】:

以上是关于RangeError:使用 valueChanges.subscribe 时超出了最大调用堆栈大小的主要内容,如果未能解决你的问题,请参考以下文章

RangeError:WebAssembly.Instance():内存不足:wasm 内存

RangeError:超出最大调用堆栈大小

淘汰赛未捕获 RangeError:超过最大调用堆栈大小

形式:手动触发valueChange

颤振错误:RangeError(索引):无效值:不在0..2范围内,包括:3

RangeError:超过最大调用堆栈大小 React Native