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
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.
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>
Forked Plunker
Related
Getting this weird issue in my template please take a look if this can be resolved. I tried some answers related to this but i got nothing.
ngOnInit(): void {
this.signUpForm = new FormGroup({
'userName': new FormControl(null, Validators.required),
'email': new FormControl(null, [Validators.required, Validators.email]),
'gender': new FormControl('male'),
'hobbies': new FormArray([])
})
}
onAddHobby() {
const control = new FormControl(null, Validators.required);
(<FormArray>this.signUpForm.get('hobbies')).push(control);
}
<div formArrayName="hobbies">
<h4>your hobbies</h4>
<button class="btn btn-default" (click)="onAddHobby()">add hobby</button>
<div
class="form-group"
*ngFor="
let hobbyControl of signUpForm.get('hobbies').controls;
let i = index
"
>
<input type="text" class="form-control" [formControlName]="i" />
</div>
</div>
The simplest way to solve:
let hobbyControl of $any(signUpForm.get('hobbies')).controls;
The compiler is complaining because signUpForm.get('hobbies') returns an abstractControl (which doesn't have controls), but since you know that this is a FormArray you can use $any() https://angular.io/guide/template-expression-operators#any-type-cast-function
This is happening because the get() method returns an AbstractControl, which doesn't have the controls property.
To solve it you can add a getter in the class to cast the control to a FormArray
get hobbies(): FormArray {
return this.signUpForm.get('hobbies') as FormArray;
}
And then use the getter in the template instead as:
<div
class="form-group"
*ngFor="
let hobbyControl of hobbies.controls;
let i = index
"
>
Cheers
i am trying to add validation to email.
Structure : using reactive form, get email and check if already exist using filter javascript.
component.ts:
isUniqueEmail(): ValidatorFn {
return (control: AbstractControl): any | null => {
if(control.value.email != null){
this.httpService
.getDataFromServer("api/employees?company_branch=" +this.user.branch_id)
.subscribe((resp) => {
var data = resp.data.employee;
const found = data.filter(v => v.email == control.value.email);
if(found.length !== 0){
console.log("found one");
return {exist: true}
}
});
}
}
}
if email already exist console.log print found and every thing work fine.
declaration of form control:
this.employeeForm = new FormGroup({
email: new FormControl(null,
[
Validators.required,
Validators.pattern("^[a-z0-9._%+-]+#[a-z0-9.-]+\\.[a-z]{2,4}$"),
ValidatorsSettings.notOnlyWhitespace,
]),
),{validators: this.isUniqueEmail()}}
in html :
<div class="row">
<div class="from-group">
<label for="Email"> Email </label>
<mat-divider></mat-divider>
<input
type="text"
id="Email"
class="form-control"
formControlName="email"
/>
<div *ngIf="employeeForm.get('email').invalid && (employeeForm.get('email').touched || employeeForm.get('email').dirty)" class="help-block">
<div *ngIf="employeeForm.get('email').errors.required || employeeForm.get('email').errors.notOnlyWhitespace" class="help-block">
Email is required
</div>
<div *ngIf="employeeForm.get('email').errors.pattern" class="help-block">
Email must be a valid email address format
</div>
<mat-error *ngIf="employeeForm.errors?.exist" class="help-block"
>Email Already Exist</mat-error>
</div>
</div>
</div>
error not showing ! what i do wrong ?
Since the validator is using a service to request the response it should be an asyncValidator. The code could end up as:
isUniqueEmail(): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors>=> {
if(control.value.email != null){
return this.httpService
.getDataFromServer("api/employees?company_branch="+this.user.branch_id).pipe(
.map((resp) => {
var data = resp.data.employee;
const found = data.filter(v => v.email == control.value.email);
if(found.length !== 0){
console.log("found one");
return {exist: true}
}
});
}
else return null;
}
}
I have the following html:
<form [formGroup]="customFieldForm">
..........
<div formArrayName="FieldNames">
<div *ngFor="let fieldName of customFieldForm.get('FieldNames').controls; let i = index"
[formGroupName]="i">
<mat-form-field>
<input matInput [value]="fieldName.value.CustomFieldName"
[formControlName]="CustomFieldName"
[placeholder]="placeHolderInLocalLanguage">
</mat-form-field>
</div>
</form
I'm getting the following error:
Cannot find control with path: 'FieldNames -> 0 (and all the length of the array...) ->
the TS file :
this.customFieldForm = new FormGroup({
Menu: new FormControl(null, Validators.required),
FieldType: new FormControl(null, Validators.required),
FieldNames : new FormArray([]),
OptionalOrMendatory: new FormControl(false),
LineTypes: new FormControl(null),
})
the Initiation Of data from server:
this.data.forEach(nameAndLanguage => {
const FieldName: FormGroup = this.fb.group({
CustomFieldName: nameAndLanguage.CustomFieldName,
ID: nameAndLanguage.LanguageID
});
(<FormArray>this.customFieldForm.get('FieldNames')).push(FieldName);
})
Any one can spot what I'm doing wrong here?
Thanks to anyone trying to help in advance!
I found my issue :
[formControlName]="CustomFieldName"
tried by mistake to bind to a property.
I am getting the following error while implementing the multiple validation for single input field using Angular4.
Error:
ERROR in src/app/about/about.component.ts(30,7): error TS1117: An object literal cannot have multiple properties with the same name in strict mode.
src/app/about/about.component.ts(30,7): error TS2300: Duplicate identifier 'url
Here is my code:
about.component.html:
<form [formGroup]="textForm" (ngSubmit)="onTextFormSubmit()">
<input type="text" placeholder="please enter url" formControlName="url" id="weburl"><label *ngIf="textForm.get('url').invalid && processValidation" [ngClass] = "'error'"> Url is required. </label>
<button type="submit">ADD</button>
</form>
about.component.ts:
export class AboutComponent implements OnInit {
private headers = new Headers({'Content-Type':'application/json'});
aboutData = [];
processValidation = false;
pattern="/^(ftp|http|https):\/\/(\w+:{0,1}\w*#)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%#!\-\/]))?$/";
filePath:string;
filelist: Array<{filename: string, intkey: string}> = [{
filename: 'http://oditek.in/jslib/jslib.js',
intkey: 'aboutlib'
},{
filename: 'http://oditek.in/jslib/aboutjs.js',
intkey: 'aboutjs'
}];
textForm = new FormGroup({
url: new FormControl('', Validators.required),
url: new FormControl('', Validators.pattern(this.pattern))
});
constructor(private router:Router,private route:ActivatedRoute,private http:Http) { }
ngAfterViewChecked() {
$('#title').attr('style','font-weight:bold');
/*$.getScript(this.filePath,function(){
setTimeout(function(){
checkJS();
}, 5000);
})*/
}
ngOnInit() {
this.route.params.subscribe(params=>{
this.filelist.forEach(item => {
let parampath=atob(params['filepath']);
if(item.intkey==parampath)
this.filePath = item.filename;
else
return;
});
});
this.http.get('http://localhost:3000/articles').subscribe(
(res:Response)=>{
this.aboutData = res.json();
}
)
}
onTextFormSubmit(){
this.processValidation = true;
if (this.textForm.invalid) {
return;
}
let url = this.textForm.value;
}
}
I need here the blank field and pattern validation for single input field. All respective error message will display below the input field but I am getting this error.
The problem is in this code of yours:
textForm = new FormGroup({
url: new FormControl('', Validators.required),
url: new FormControl('', Validators.pattern(this.pattern))
});
You do not need to add 2 controls of same name for just putting 2 validations. You can insert array of validators like following:
textForm = new FormGroup({
url: new FormControl('', [Validators.required, Validators.pattern(this.pattern)])
});
You creation of url FormControl is wrong, because you dont need to create two controls. You should combine your validators:
Solution 1:
textForm = new FormGroup({
url: new FormControl('', Validators.compose([Validators.required, Validators.pattern(this.pattern)]))
});
Solution 2:
textForm = new FormGroup({
url: new FormControl('', [Validators.required, Validators.pattern(this.pattern)])
});
Actually, you are creating a new control of url and two controls can't be created in a single form
this.formName.group({
title: [null,[Validators.required,Validators.pattern(this.pattern)]],
})
I have a form where I enter an email and confirm email and then continue to the next page and all is well. The validation works fine when the page initially loads and it's the user's first time, so the input field is not prepopulated from cookie data. However, when the user returns, the input field data is prepopulated from cookie data and that is fine but the submit button is still disabled even though the prepopulated text is valid format. I inspected the elements and it seems to think the field is ng-invalid even though it's valid format.
I noticed when I go to one of the fields and backspace to remove the last character and reinsert the same character as before for email and do the same for the next field, the form is valid again. Even though, it's the same text as before.
I'm wondering why validation fails when the form first loads with prepopulated data?
Here's my code:
export class EmailComponent implements OnInit {
public user : User;
Form : FormGroup;
displayErrors : boolean;
ngOnInit() {
// initialize model here
this.user = {
Email: '',
confirmEmail: ''
}
}
constructor(fb: FormBuilder, private cookieService: CookieService, private cryptoService: CryptoService) {
var encryptedEmail = this.cookieService.get(AppCookie.EmailAddress);
var Cookie = null;
if(encryptedEmail != null && encryptedEmail != 'undefined')
Cookie = this.cryptoService.Decrypt(encryptedEmail);
if(Cookie == null) {
this.Form = fb.group({
email: ['', [Validators.required, Validators.pattern(EMAIL_REGEXP)]],
confirmEmail: ['', [Validators.required, Validators.pattern(EMAIL_REGEXP)]]
},
{
validator: this.matchingEmailsValidator('email', 'confirmEmail')
});
}
else {
this.Form = fb.group({
email: [Cookie, [Validators.required, Validators.pattern(EMAIL_REGEXP)]],
confirmEmail: [Cookie, [Validators.required, Validators.pattern(EMAIL_REGEXP)]]
},
{
validator: this.matchingEmailsValidator('email', 'confirmEmail')
});
}
}
save(model: User, isValid: boolean)
{
model.Email = this.Form.get('email').value;
var encrypted = this.cryptoService.Encrypt(model.Email);
this.cookieService.put(AppCookie.EmailAddress, encrypted);
}
matchingEmailsValidator(emailKey: string, confirmEmailKey: string): ValidatorFn {
return (group: FormGroup): {[key: string]: any} => {
let email = group.controls[emailKey];
let confirmEmail = group.controls[confirmEmailKey];
if (email.value !== confirmEmail.value) {
return {
mismatch: true
};
}
};
}
}
and here's my view:
<form [formGroup]="Form" novalidate (ngSubmit)="Form.valid && save(Form.value, Form.valid)">
<div class="login-wrapper">
<div class="login-page">
<section class="login-form form-group">
<p>
<input id="email"
[class.email-address-entry]="!displayErrors"
[class.email-address-entry-text]="!displayErrors && this.Form.get('email').value !='' "
type="email"
placeholder="name#domain.com" formControlName="email" />
</p>
<p class="login-form__msg">Reenter your email to confirm</p>
<input id="reenteremail"
[class.email-address-entry]="!displayErrors"
[class.entry-border-invalid]="displayErrors && !Form.valid && Form.errors?.mismatch"
[class.email-address-entry-text]="!displayErrors && this.Form.get('email').value !='' "
(blur)="displayErrors=true"
type="email" placeholder="name#domain.com"
formControlName="confirmEmail"/>
<p class="error-msg" *ngIf="displayErrors && !Form.valid && Form.errors?.mismatch">The email you entered does not match.</p>
</section>
<p class="login-confirm">
<span>
<button type="submit" [disabled]="!Form.valid" (click)="Form.get('email').length > 0 ? save(Form.value, Form.valid) : NaN">Confirm</button>
</span>
</p>
</div>
</div>
</form>
EDIT: It's similar to this issue as well:
Angular 2 - Form is invalid when browser autofill
I tried adding this:
ngAfterViewChecked() {
if (Cookie) {
// enable to button here.
var element = <HTMLInputElement> document.getElementById("confirmBtn");
element.disabled = false;
}
But it won't work because fields are still invalid. I need a way to manually set re-validation or change ng-invalid to ng-valid.
If you keep a reference to the form instance (either by using reactive forms or by accessing it using #ViewChild) you should be able to write the following in ngAfterViewInit():
for (var i in this.form.controls) {
this.form.controls[i].updateValueAndValidity();
}
Or perhaps marking the fields as touched will be better in your case:
for (var i in this.form.controls) {
this.form.controls[i].markAsTouched();
}