RangeError: максимальный размер стека вызовов превышен при использовании valueChanges.subscribe
Я использую 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)
"Запишите это!" неоднократно записывается в журнал, так как он называется рекурсивно, поэтому их ошибка стека
Если я удалю значениеChanges.subscribe, то код работает отдельно от удаления проверки условно.
Почему он вызывает рекурсивно вызов valueChanges.subscribe?
Ответы
Ответ 1
Проблема в том, что вы изменяете значение поля внутри обработчика события valueChanges
для этого же поля, заставляя событие запускаться снова:
this.userForm.get('loginTypeId').valueChanges.subscribe(
(loginTypeId: string) => {
...
this.userForm.get('loginTypeId').updateValueAndValidity(); <-- Triggers valueChanges!
}
Ответ 2
На самом деле, правильный ответ таков: если вы хотите подписаться на какие-либо изменения формы и по-прежнему запускать в ней patchValue, то вам просто нужно добавить параметр {emitEvent: false}
в patchValue, таким образом, исправление не вызовет другое обнаружение изменений
Код:
this.formGroup
.valueChanges
.subscribe( _ => {
this.formGroup.get( 'controlName' ).patchValue( _val, {emitEvent: false} );
} );
PS. Это также менее утомительно, чем подписка на каждый элемент управления формы один за другим, чтобы избежать запуска превышения максимального стека вызовов. Особенно, если у вас есть 100 элементов управления для подписки.
Теперь, чтобы уточнить, если вам все еще нужно обновить updateValueAndValidity внутри подписки, я предлагаю вам использовать оператор distinctUntilChanged
rxjs, чтобы запускать подписку только при изменении какого-либо значения.
Различную документацию можно найти здесь
https://www.learnrxjs.io/operators/filtering/distinctuntilchanged.html
DifferentUntilChanged - издает только тогда, когда текущее значение отличается чем последний.
Теперь нам также нужно будет сделать ее пользовательской функцией проверки, потому что по умолчанию DifferentUntilChanged проверяет объекты по указателю, и указатель является новым при каждом изменении.
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();
} );
И вуаля, мы исправляем и обновляем, не запуская максимальный стек вызовов!
Ответ 3
Мой ответ - просто разработка этого.
Добавляя distinctUntilChanged()
в конвейер перед subscribe()
, вы избегаете "Превышен максимальный размер стека вызовов", потому что
Метод DifferentUntilChanged генерирует только тогда, когда текущее значение отличается от последнего.
Использование:
this.userForm.get('password')
.valueChanges.pipe(distinctUntilChanged())
.subscribe(val => {})
Документация
Ответ 4
Попробуйте добавить distinctUntilChanged()
в конвейере непосредственно перед subscribe()
. Он должен отфильтровывать те события "изменения", значения которых фактически не изменялись.