webentwicklung-frage-antwort-db.com.de

RangeError: Maximale Aufrufstackgröße bei Verwendung von valueChanges.subscribe überschritten

Ich verwende Angular 5 mit reaktiven Formularen und muss die valueChanges verwenden, um die erforderliche Validierung dynamisch zu deaktivieren

bauteilklasse:

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

Beim Ausführen erhalte ich jedoch einen Browser-Javascript-Fehler

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)

"log dies!" wird wiederholt abgerufen, wie es rekursiv genannt wird, weshalb es einen Stack-Fehler gibt

Wenn ich valueChanges.subscribe entferne, funktioniert der Code nicht mit der bedingten Entfernung der Validierung.

Warum ruft es valueChanges.subscribe rekursiv auf?

8
dfmetro

Das Problem ist, dass Sie den Wert des Felds im valueChanges-Ereignishandler für dasselbe Feld ändern, wodurch das Ereignis erneut ausgelöst wird:

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

Versuchen Sie, distinctUntilChanged() kurz vor subscribe() in die Pipeline einzufügen. Es sollte jene "Änderungsereignisse" herausfiltern, bei denen der Wert nicht tatsächlich geändert wurde.

8

Tatsächlich ist die wahre Antwort: Wenn Sie alle Formularänderungen abonnieren möchten und darin noch patchValue ausführen, sollten Sie einfach die Option {emitEvent: false} zu patchValue hinzufügen, sodass das Patching keine weitere Änderungserkennung auslöst

code:

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

PS. Dies ist auch weniger langwierig, als jede Formularsteuerung einzeln zu abonnieren, um zu vermeiden, dass die maximale Anzahl an Aufrufstapeln überschritten wird. Besonders wenn Ihr Formular 100 Steuerelemente enthält, die Sie abonnieren können.

Wenn Sie nun noch weitere Informationen zum UpdateValueAndValidity-Wert im Abonnement benötigen, schlage ich vor, dass Sie den RXJs-Operator distinctUntilChanged verwenden, um das Abonnement nur auszuführen, wenn sich ein Wert ändert. 

Jetzt müssen wir es auch zu einer benutzerdefinierten Überprüfungsfunktion machen, da distinctUntilChanged Objekte standardmäßig durch einen Zeiger überprüft und der Zeiger bei jeder Änderung neu ist.

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

Und voila, wir patchen und aktualisieren, ohne den maximalen Call-Stack zu erreichen!

7

Meine Antwort ist nur die Entwicklung von dieser .

Durch das Hinzufügen von distinctUntilChanged() in der Pipeline vor subscribe() vermeiden Sie die "Maximale Aufrufstackgröße überschritten", weil

Die Methode distinctUntilChanged wird nur ausgegeben, wenn der aktuelle Wert vom letzten Wert abweicht.

Die Verwendung:

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

Dokumentation

5
mpro