Reputation: 1216
I am trying to validate a reactive form that has three fields. Username is always a required field, but I only want the user to have to enter either a phone number or an email in addition to username, and I cannot figure out how to do so.
I've tried nested form groups, and custom validators, I also tried the solution here, but no luck.
Here is my current html form:
<div class="container"><br>
<form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" formControlName="username" class="form-control">
<span *ngIf="!signupForm.get('username').valid && hasBeenSubmitted == true" style="color: red">Please enter a valid username!</span>
</div>
<br><hr>
<div formGroupName="userdata">
<div class="form-group">
<label for="email">email</label>
<input type="text" id="email" formControlName="email" class="form-control">
</div>
or <br>
<div>
<label for="phoneNumber">phone</label>
<input type="text" formControlName="phoneNumber">
</div>
</div>
<hr>
<button class="btn btn-primary" type="submit">Submit</button>
<span *ngIf="!signupForm.get('userdata.email').valid && hasBeenSubmitted == true && hasBeenSubmitted == true && !signupForm.get('userdata.email').valid
&& hasBeenSubmitted == true" style="color: red">Please enter a valid email or phone number!</span>
</form>
</div>
And my typescript:
export class AppComponent implements OnInit {
signupForm: FormGroup;
hasBeenSubmitted = false;
ngOnInit() {
this.signupForm = new FormGroup({
'username': new FormControl(null, Validators.required),
'nickname': new FormControl(null, Validators.required),
// Nested formgroup:
'userdata': new FormGroup({
'email': new FormControl(null, [Validators.required, Validators.email]),
'phoneNumber': new FormControl(null, [Validators.required]),
}),
});
}
onSubmit() {
this.hasBeenSubmitted = true; // I use this variable to validate after submission
}
}
When the user submits a form without an email or phone number, I want the error text to read: Please enter a valid email or phone number!
. Which will resolve when the user starts to enter a valid email or phone number.
How can I validate my form so that the user only needs to enter a username and a phone number, or a username and an email, for the form to be valid?
Plunkr for my current code: https://plnkr.co/edit/1IyVQblRX1bZxOXIpyQF?p=preview
Upvotes: 2
Views: 2011
Reputation: 7239
I created a custom validator directive:
import {
FormGroup,
ValidationErrors,
ValidatorFn,
Validators,
} from '@angular/forms';
export const atLeastOne = (validator: ValidatorFn, controls:string[] = null) => (
group: FormGroup,
): ValidationErrors | null => {
if(!controls){
controls = Object.keys(group.controls)
}
const hasAtLeastOne = group && group.controls && controls
.some(k => !validator(group.controls[k]));
return hasAtLeastOne ? null : {
atLeastOne: true,
};
};
To use it, you just do this:
this.signupForm = new FormGroup({
'username': new FormControl(null, Validators.required),
'nickname': new FormControl(null, Validators.required),
'userdata': new FormGroup({
'email': new FormControl(null),
'phoneNumber': new FormControl(null),
}, { validator: atLeastOne(Validators.required, ['email','phoneNumber']) })
});
So email
or phoneNumber
would be required here. If you leave it empty then any control with a value is fine and you can use it with any type of validator, not just Validators.required
.
This is reusable in any form.
Upvotes: 3
Reputation: 214095
As you have said you can use custom validator for group:
this.signupForm = new FormGroup({
'username': new FormControl(null, Validators.required),
'nickname': new FormControl(null, Validators.required),
// Nested formgroup:
'userdata': new FormGroup({
'email': new FormControl(null),
'phoneNumber': new FormControl(null),
}, this.validateUserData)
});
where validateUserData
method looks like:
validateUserData(formGroup) {
const emailCtrl = formGroup.get('email');
const phoneCtr = formGroup.get('phoneNumber');
if (emailCtrl.value && !Validators.email(emailCtrl) || phoneCtr.value) {
return null;
}
return { required: true };
}
and last thing you need to do is change template like:
<span *ngIf="!signupForm.get('userdata').valid && hasBeenSubmitted" ...>
Please enter a valid email or phone number!
</span>
Upvotes: 1