Here is the demo plnkr. I am trying to implement a custom async validator for a OTP input which has a text box and a verify button. I want to validate the input only when the user clicks on the verify OTP button or form submit button. Currently validation is happening on the text change event and it's not working. Form HTML:
<form [formGroup]="registrationForm" (ngSubmit)="registrationForm.valid && submitRegistration(registrationForm.value)" novalidate>
<fieldset class="form-group">
<label for="e-mail">Mobile</label>
<div class="input-group">
<input type="text" class="form-control" placeholder="Mobile" formControlName="mobile">
<span class="input-group-btn">
<button class="btn btn-secondary" type="button">Send OTP</button>
</span>
</div>
<div class="form-text error" *ngIf="registrationForm.controls.mobile.touched">
<div *ngIf="registrationForm.controls.mobile.hasError('required')">Mobile is required.</div>
</div>
</fieldset>
<fieldset class="form-group">
<label for="e-mail">Verify OTP</label>
<div class="input-group">
<input type="text" class="form-control" placeholder="OTP" formControlName="otp">
<span class="input-group-btn">
<button class="btn btn-secondary" (click)="veryOTPAsyn(otp)" type="button">Verify</button>
</span>
</div>
<div class="form-text error" *ngIf="registrationForm.controls.otp.touched">
<div *ngIf="registrationForm.controls.otp.hasError('required')">OTP is required.</div>
<div *ngIf="registrationForm.controls.otp.hasError('invalidOtp')">OTP is invalid.</div>
</div>
</fieldset>
<button class='btn btn-primary' type='submit' [disabled]='!registrationForm.valid'>Submit Registration Form</button>
</form>
Form Component:
export class ExampleFormComponent {
registrationForm: FormGroup;
constructor(public fb: FormBuilder) {
// Example use of FormBuilder, FormGroups, and FormControls
this.registrationForm = fb.group({
mobile: ['', Validators.required],
otp: ['', Validators.compose([Validators.required, this.veryOTPAsyn.bind(this)])],
dob: ['', Validators.required],
email: ['', Validators.compose([Validators.required, emailValidator])],
password: ['', Validators.required],
confirmPassword: ['', Validators.required],
firstName: ['', Validators.required],
lastName: ['', Validators.required]
}, {validator: matchingPasswords('password', 'confirmPassword')})
}
submitRegistration(value: Object): void {
console.log(value);
}
veryOTPAsyn(otpControl: FormControl): Promise<any> {
console.log(otpControl)
console.log(otpControl.hasError('invalidOtp'))
return new Promise<any>(
(resolve, reject) => {
setTimeout(() => {
resolve({invalidOtp:true});
}, 500);
});
}
}
Try this :
otp: ['', Validators.required, this.veryOTPAsyn.bind(this)]
Async validators go in 3rd argument
I would consider to remove the custom validation and instead set / remove error when user clicks on button instead, since the custom validators will run when component is initialized, as well as whenever input value changes. So have your click event, and do something like this (pseudo code):
(click)="veryOTPAsyn(registrationForm.controls.otp)"
and TS:
veryOTPAsyn(ctrl: FormControl) {
if(ctrl.value == 'hey') {
ctrl.setErrors({'invalidOtp':true})
} else {
ctrl.setErrors({'invalidOtp':null})
}
}
Forked PLUNKER.
EDIT:
But if you want to go the route of using the async validator, just add the validator as third argument, but then you don't need the 'validate' button.
otp: ['', [Validators.required], [this.veryOTPAsyn]]
or do it like it's done now after the release of v 5.0.0
otp: ['', {validators: [Validators.required], asyncValidators: [this.veryOTPAsyn]}]
PLUNKER (pre v.5.0.0, so the first option of marking async validator is used)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With