I am using Angular 5 with Reactive forms and need to make use of the valueChanges in order to disable required validation dynamically
component class:
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
});
}
However when I run it I get a browser javascript error
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 this!" is loggedcalled repeatedly like it is called recursively which is why their is a stack error
If I remove the valueChanges.subscribe the code work apart from removing the validation conditionally.
Why is it calling valueChanges.subscribe recursively?
The problem is that you modify the value of the field inside of the
valueChanges
event handler for that same field, causing the event to be triggered again:If you want to subscribe to any form changes and still run patchValue inside it, then you could add the
{emitEvent: false}
option to patchValue, thus the patching will not trigger another change detectioncode:
PS. This is also less tedious than subscribing to each form control one-by-one to avoid triggering change max call stack exceeded. Especially if you form has 100 controls to subscribe to.
Now to elaborate further, if you still need to updateValueAndValidity inside the subscription, then I suggest you use the
distinctUntilChanged
rxjs operator, to only run the subscription, when some value changes.distinctUntilChanged documentation can be found here
https://www.learnrxjs.io/operators/filtering/distinctuntilchanged.html
Now we will also have to make it a custom validation function, because by default, distinctUntilChanged validates objects by pointer and the pointer is new on every change.
And voila, we are patching and updating, without running into the maximum call stack!
My answer is just development of this one.
By adding
distinctUntilChanged()
in the pipeline just beforesubscribe()
you avoid the "Maximum call stack size exceeded" becauseThe usage:
Documentation
Try adding
distinctUntilChanged()
in the pipeline just beforesubscribe()
. It should filter out those "change" events where value was not actually changed.