I encountered the same issue, since I couldn't find anyway to set different update strategies on the same input field, I found a nice workaround, I explain it in the example below:
In a signup form, I had the input field for an email for which I needed two validators with two different strategies:
One to check the email is actualy formatted as an email, with a sync validator on client side with the built-in validator Validators.email, I want this one to be with updateOn: "change" so it is checked as the user is typing
Another to check in the DB if the email doesn't already exists with an async API CALL, I want this one to be done with updateOn: "blur" (when the user leaves the input) and only if the email is well formatted as an email (so when first validator is valid).
The form group creation in my ts file where the first validator is declared, no need to set updateOn: "change" since it's the value by default:
public myForm: FormGroup;
private emailSubscription: Subscription;
this.myForm = this.fb.group({
// ... some other inputs
email : ["", {validators: Validators.email}]
})
And I made the async validator in a subcription to the form changes in the AfterViewInit after a filter on emailControl.touched that equals the updateOn: "blur", then , not relevant here, it checks email doesn't exist with an API CALL
ngAfterViewInit() {
// Gets input control
const emailControl = this.signupForm.get("email");
// Subscribes to the input control's valueChanges Observable
this.emailSubcription = emailControl.valueChanges
.pipe(
// Checks only when email input is left and valid (so when
// the "genuine" Vaildators.email is verified
filter((checkedEmail) => emailControl.touched && emailControl.valid),
// API CALL checking if email exists, we use switchMap to end previous
// call if user keep typing and result has not been returned yet
switchMap((inputEmail) => {
// variable made to send data to server with API CALL,
// not relevant here
const checkedEmail = { checkedEmail: inputEmail.toLowerCase() };
// API call to check email exists, not relevant here,
// could be any async method
return this.http.post("/api/user/emailExists", checkedEmail);
})
)
.subscribe((emailExists: boolean) => {
// Sets an error on the input control if the email exists,
// so the error can be then processed as needed (for example
// to show a div in template or use with mat-error of the input)
if (emailExists === true) {
emailControl.setErrors(
{ emailNotAvailable: true },
{ emitEvent: false }
);
}
});
}
At last, do not forget to unsubscribe the observable:
public ngOnDestroy() {
if (this.emailSubscription) this.emailSub.unsubscribe();
}