I am new in angular. I have one scenario where I need only one field required from 5 fields in the form, means if the user fills at least one field then form makes valid.
Thanks in advance.
Since you need to check for the validity of whole form only if one of the fields is non empty , You can manually set the validity like below :
if(!this.valid){
this.form.setErrors({ 'invalid': true});
}else{
this.form.setErrors(null);
}
Where this.valid is your condition based on which you can set the validity
You can check the example : https://angular-exmphk.stackblitz.io
You can also check the answer : FormGroup validation in "exclusive or" which does form validation based on some condition
Hope this helps
See Custom Validators and Cross-field validation in https://angular.io/guide/form-validation
Exact example from our app, where at least one phone number field must be entered. This is a Validator Function, i.e. implements https://angular.io/api/forms/ValidatorFn
import { AbstractControl } from "#angular/forms";
import { Member } from "../../members/member";
export function phone(control: AbstractControl): {[key: string]: any} {
if (!control.parent) return;
const form = control.parent;
const member: Member = form.value;
if (member.contactphone || member.mobile || member.contactphonesecond) {
[
form.controls['contactphone'],
form.controls['mobile'],
form.controls['contactphonesecond']
].forEach(control => {
control.setErrors(null);
});
} else return {'noPhone': 'None of contact phones is specified'};
}
Member is our class that defines all the form fields, your code will be different but the example of the custom validator should help.
Check this example of phone number validator
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { NumberValidator } from '../validators/form-validators';
constructor(
private fb: FormBuilder){}
FormName: FormGroup = this.fb.group({
phoneNumber: ['', NumberValidator]
});
in form-validator file
import { AbstractControl, ValidatorFn } from '#angular/forms';
export const NumberValidator = (): ValidatorFn => {
return (control: AbstractControl): { [key: string]: any } | null => {
const mobileRegex = /^[()-\d\s]*$/g;
const result = mobileRegex.test(control.value);
return result ? null : { mobileInvalid: { value: control.value } };
};
};
let me know if you have any doubts.
<form [formGroup]="formdata">
<div class="form-group">
<label for="fieldlabel1">fieldLabel1</label>
<input type="text" id="fieldlabel1" formControlName="fieldName1" class="form-control"><br>
<label for="fieldlabel2">fieldLabel2</label>
<input type="text" id="fieldlabel2" formControlName="fieldName2" class="form-control">
</div>
</form>
<div class="row">
<div class="col-sm-12">
<button type="submit" value="submit" (click)="somefunctioncall()" [disabled]="formdata.invalid">
Submit
</button>
</div>
</div>
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { OnInit } from '#angular/core';
export class test {
formdata: FormGroup;
ngOnInit() {
this.formdata = new FormGroup({
fieldName1: new FormControl("", Validators.compose([
Validators.required
])),
fieldName2: new FormControl("", Validators.compose([
// remove required validation for one you dont need.
]))
})
}
}
Related
I'm trying to create a material username reactive form control using the approach outlined in this tutorial.
The end result should work in a form like this ( the fs-username-form is the custom reactive form component ):
<mat-card class="UsernameFormTestCard">
<form class="UsernameForm" [formGroup]="form" (ngSubmit)="submit()">
<mat-form-field>
<input
matInput
placeholder="First name"
type="text"
formControlName="firstName"
/>
<mat-hint *ngIf="!username">Example Monica</mat-hint>
<mat-error>Please enter your first name</mat-error>
</mat-form-field>
<fs-username-form formControlName="username"></fs-username-form>
<button mat-button type="submit" [disabled]="!form.valid">Submit</button>
</form>
</mat-card>
If the form is valid then the Submit button enables.
And it works ... sort of ... It enables reactively ... but not consistently. The minimum number of characters is set to 4. And the submit button enables when the length is 4. However if we start removing characters from the username the submit button only disables after the length of the username is 2.
This is the Stackblitz demo showing this.
The username-form.component.ts is implemented like this:
import { Component } from '#angular/core';
import {
AbstractControl,
ControlValueAccessor,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
Validators,
} from '#angular/forms';
#Component({
selector: 'fs-username-form',
templateUrl: './username-form.component.html',
styleUrls: ['./username-form.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: UsernameFormComponent,
},
{
provide: NG_VALIDATORS,
multi: true,
useExisting: UsernameFormComponent,
},
],
})
export class UsernameFormComponent implements ControlValueAccessor, Validator {
//=============================================
// ControlValueAccessor API Methods
//=============================================
disabled: boolean = false;
// Dummy initialization.
//The implementation is passed in
// with registerOnChange.
onChange = (username: string) => {};
onTouched = () => {};
touched = false;
usernameValue = '';
writeValue(username: any): void {
this.usernameValue = username;
}
//=============================================
// Registration API Methods
//=============================================
registerOnChange(onChange: any): void {
this.onChange = onChange;
}
registerOnTouched(onTouched: any): void {
this.onTouched = onTouched;
}
markAsTouched() {
if (!this.touched) {
this.onTouched();
this.touched = true;
}
}
setDisabledState(disabled: boolean) {
this.disabled = disabled;
if (disabled) {
this.usernameControl?.disable();
} else {
this.usernameControl?.enable();
}
}
//=============================================
// Validator API Methods
//=============================================
validate(control: AbstractControl): ValidationErrors | null {
console.log('VALIDATE IS GETTING CALLED');
console.log('Is the form valid: ', this.usernameForm.valid);
if (this.usernameForm.valid) {
return null;
}
let errors: any = {};
errors = this.addControlErrors(errors, 'username');
return errors;
}
addControlErrors(allErrors: any, controlName: string) {
const errors = { ...allErrors };
const controlErrors = this.usernameForm.controls[controlName].errors;
if (controlErrors) {
errors[controlName] = controlErrors;
}
return errors;
}
/**
* Registers a call to onChange to inform
* parent forms of valueChanges events.
*/
constructor() {
this.usernameControl?.valueChanges.subscribe((u) => {
this.onChange(u);
});
}
public usernameForm: FormGroup = new FormGroup({
username: new FormControl('', [
Validators.required,
Validators.minLength(4),
]),
});
get username() {
return this.usernameForm.get('username')?.value;
}
get usernameControl() {
return this.usernameForm.get('username');
}
}
The username-form.component.html template looks like this:
<form class="UsernameForm" [formGroup]="usernameForm">
<mat-form-field>
<input
matInput
placeholder="username"
type="text"
formControlName="username"
/>
<mat-hint *ngIf="!username">Example Monica</mat-hint>
<mat-error>Please enter your username</mat-error>
</mat-form-field>
</form>
As can be seen it implements the ControlValueAccessor and Validator interfaces.
Call on onChange are made when the user types in the username field and, if I understand correctly, this in turn should cause the parent form to call validate on the username control.
Why does the submit button in the demo form not update consistently or why the username-form component does not?
Got it working. Instead of checking whether the form containing the control is valid in the validate method, I switched it to check whether the control itself is valid like this:
//=============================================
// Validator API Methods
//=============================================
validate(control: AbstractControl): ValidationErrors | null {
if (this.usernameControl.valid) {
return null;
}
let errors: any = {};
errors = this.addControlErrors(errors, 'username');
return errors;
}
And now it works. This is a new working demo.
I want to implement custom validation in Angular 11.
My component.ts is as follows:
import { Component, OnInit } from '#angular/core';
import { FormControl, PatternValidator } from '#angular/forms';
#Component({
selector: 'app-microplate',
templateUrl: './microplate.component.html',
styleUrls: ['./microplate.component.css']
})
export class MicroplateComponent {
columns = new FormControl('', isValid(???));
isValid(???) {
return some logic based on the ???;
}
}
My component.html is as follows (without any tag. Just as shown here):
<label for="columns"><b>Columns: </b></label>
<input id="columns" type="text" [formControl]="columns">
In the above code, by ??? I mean the value of the columns field, but I don't know how to send the value of the columns field to the isValid() method. I don't know how to do a custom validation even after hours of searching in google. I also want to show an error message in the component.html if the isValid() method returns false, but I don't know how to do that.
defining a custom validator is already well explained in the docs
import { AbstractControl, ValidatorFn } from "#angular/forms";
export function isValid(): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } => {
// ??? <--> control.value
};
}
As written on Angular.io:
https://angular.io/guide/form-validation#adding-custom-validators-to-reactive-forms
You can insert a directive, export it and insert it into a FormControl.
this.heroForm = new FormGroup({
name: new FormControl(this.hero.name, [
Validators.required,
forbiddenNameValidator(/bob/i) // <-- Here's how you pass in the custom validator.
])
});
forbiddenNameValidator is:
import { AbstractControl, ValidatorFn } from "#angular/forms";
/** A hero's name can't match the given regular expression */
export function forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
return (control: AbstractControl): { [key: string]: any } | null => {
const forbidden = nameRe.test(control.value);
return forbidden ? { forbiddenName: { value: control.value } } : null;
};
}
Below is a link with an example of implementation:
https://stackblitz.com/edit/angular-ivy-m89a31?file=src%2Fapp%2Fapp.component.ts
Using Angular Reactive Forms for validation of Email.
I added Validators Required and Validators Email but Its displaying both as shown in below image. I just want one Error to be displayed at a time.
HTML Code :
<form [formGroup]="NamFomNgs">
<label>Email :
<input type="email" name="MylHtm" formControlName="MylNgs">
</label><br>
<div class="ErrMsgCls" *ngIf="(NamFomNgs.controls['MylNgs'].touched || NamFomNgs.controls['MylNgs'].dirty) &&
!NamFomNgs.controls['MylNgs'].valid">
<span *ngIf="NamFomNgs.controls['MylNgs'].errors.required">This field is required</span>
<span *ngIf="NamFomNgs.controls['MylNgs'].errors.email">Enter valid email</span>
</div><br>
<button [disabled]="!NamFomNgs.valid">Submit</button>
</form>
Typescript Code :
NamFomNgs:FormGroup;
constructor(private NavPkjVaj: ActivatedRoute, private HtpCncMgrVaj: HttpClient,private FomNgsPkjVaj: FormBuilder)
{
this.NamFomNgs = FomNgsPkjVaj.group(
{
MylNgs:[null,Validators.compose([
Validators.required,
Validators.email ])]
});
}
I feel it's a bug in angular form.
You can create common component to display validation message:
custom-validation-with-error-message
HTML:
<div *ngIf="errorMessage !== null">
{{errorMessage}}
</div>
constrol-message.component.ts:
import { OnInit } from '#angular/core';
import { Component, Input } from '#angular/core';
import { FormGroup, FormControl, AbstractControl } from '#angular/forms';
import { CustomValidationService } from '../custom-validation.service'
export class ControlMessageComponent implements OnInit {
ngOnInit() {
}
#Input() control: FormControl;
constructor() { }
/**
* This method is use to return validation errors
*/
get errorMessage() {
for (let propertyName in this.control.errors) {
if (this.control.errors.hasOwnProperty(propertyName) && this.control.touched) {
return CustomValidationService.getValidatorErrorMessage(this.getName(this.control), propertyName, this.control.errors[propertyName]);
}
if (this.control.valueChanges) {
return CustomValidationService.showValidatorErrorMessage(propertyName, this.control.errors[propertyName])
}
}
return null;
}
/**
* This method used to find the control name
* #param control - AbstractControl
*/
private getName(control: AbstractControl): string | null {
let group = <FormGroup>control.parent;
if (!group) {
return null;
}
let name: string;
Object.keys(group.controls).forEach(key => {
let childControl = group.get(key);
if (childControl !== control) {
return;
}
name = key;
});
return name;
}
}
I need to assign a custom validator to a FormGroup. I can do this at the time the FormGroup is created like this:
let myForm : FormGroup;
myForm = this.formBuilder.group({
myControl1: defaultValue,
myControl2: defaultValue
}, { validator: this.comparisonValidator })
comparisonValidator(g: FormGroup) {
if (g.get('myControl1').value > g.get('myControl2'.value)) {
g.controls['myControl1'].setErrors({ 'value2GreaterThanValue1': true });
return;
}
}
I have a situation though where I need to add the custom validator after I've instantiated the FormGroup, so I'm trying to do this after instantiating myForm, instead of adding the validator when the form is instantiated:
let myForm : FormGroup;
myForm = this.formBuilder.group({
myControl1: defaultValue,
myControl2: defaultValue
})
this.myForm.validator = this.comparisonValidator;
This gives me a compiler error:
Type '(g: FormGroup) => void' is not assignable to type 'ValidatorFn'.
Type 'void' is not assignable to type 'ValidationErrors'.
How do I assign a validator to my FormGroup so that the formGroup is passed as the argument to my comparisonValidator function?
Update - I've added a line showing where I'm doing a setErrors in my comparisonValidator, to make it clearer exactly how I'm trying to set a validation error.
I've created a stackblitz take a look.
In the component.ts file
import { Component } from '#angular/core';
import {FormBuilder,FormGroup, ValidationErrors, ValidatorFn} from '#angular/forms'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
myForm: FormGroup;
defaultValue = 20;
constructor(private formBuilder: FormBuilder) {
this.myForm = this.formBuilder.group({
myControl1: this.defaultValue,
myControl2: this.defaultValue
});
debugger
this.myForm.setValidators(this.comparisonValidator())
}
public comparisonValidator() : ValidatorFn{
return (group: FormGroup): ValidationErrors => {
const control1 = group.controls['myControl1'];
const control2 = group.controls['myControl2'];
if (control1.value !== control2.value) {
control2.setErrors({notEquivalent: true});
} else {
control2.setErrors(null);
}
return;
};
}
}
In the component.html file
<div>
<form [formGroup]="myForm">
<input formControlName="myControl1" type="number">
<input formControlName="myControl2" type="number">
<br>Errors: {{myForm.get('myControl2').errors | json}}
</form>
</div>
For setting any validators (predefined or customs) after the instantiating the formGroup, you will need to use the setValiators() method of FormGroup.
For Ex:
let myFormGroup = this. _fb.group({
control1: new FormControl('1', [])
});
myFormGroup.setValidators(this.customValidators());
customValidators(): ValidatorFn {
let myFun = (cc: FormGroup): ValidationErrors => {
if(cc.valid) return null;
else return {something: 'someError'};
};
return myFun;
}
Thanks to #Anuradha Gunasekara - his answer is the most correct and complete solution. A 'quick fix' for my error was just to add a return type of any on the validator. I can still assign the custom validator to the FormGroup, and the FormGroup will be passed implicitly as the argument to my custom validator. This code will work:
let myForm : FormGroup;
myForm = this.formBuilder.group({
myControl1: defaultValue,
myControl2: defaultValue
})
this.myForm.validator = this.comparisonValidator;
comparisonValidator(g: FormGroup) : any {
if (g.get('myControl1').value > g.get('myControl2'.value)) {
g.controls['myControl1'].setErrors({ 'value2GreaterThanValue1': true });
}
}
Remove all form control white space form Form Group
custom validator :
export function formGroupRemoveWhitespaceValidator(form: FormGroup): ValidationErrors | null {
const keys: string[] = Object.keys(form.controls);
if (keys.length) {
keys.forEach(k => {
let control = form.controls[k];
if (control && control.value && !control.value.replace(/\s/g, '').length) {
control.setValue('');
}
});
}
return null;
}
component :
let myForm : FormGroup;
myForm = this.formBuilder.group({
myControl1: defaultValue,
myControl2: defaultValue
}, { validators: formGroupRemoveWhitespaceValidator });
These answers doesn't work for me, because I have other validators too. In my usecase, I have to set the errors also for the opposite field.
Perhaps it doesn't met the requirements of the OP exactly, but it hopefully helps others (like me), which landed on this site.
Here my is solution, which can be safely combined with other validators and also ensure, that the opposite field got an update.
// ...
this.form = this.formBuilder.group({
myControl1: this.formBuilder.control(defaultValue, [Validators.required, this.validateIsEqualTo('myControl2')]),
myControl2: this.formBuilder.control(defaultValue, this.validateIsEqualTo('myControl1')
});
this.form.get('myControl1').valueChanges
.pipe(
takeUntil(this.destroy$),
disinctUntilChanged())
.subscribe(() => this.form.get('myControl2').updateValueAndValidity());
this.form.get('myControl2').valueChanges
.pipe(
takeUntil(this.destroy$),
disinctUntilChanged())
.subscribe(() => this.form.get('myControl1').updateValueAndValidity());
// ...
public validateIsEqualTo(otherComponentId): ValidatorFn {
return (control: FormControl): ValidationErrors => {
const otherControl = this.form.get(otherComponentId);
return control.value !== otherControl.value ? {notEquivalent: true} : null;
}
}
I'm making a reactive from in anuglar2. When trying to submit my form, the form says its invalid. I know what is causing the issue, but not how to fix it. For one of my form controls I created a custom validator that checks if its a number. So its required and it has to be a number. If I remove my custom validation on the form, it goes back to being valid. How do I fix this so i can keep my custom validation ?
contact.component.ts
import { Component } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms'
import { ValidationService } from './validation.service'
#Component({
selector:'contact',
providers:[ValidationService],
templateUrl:'./contact.component.html'
})
export class ContactComponent {
public TicketForm = null ;
projects:Array<string> = ['Project One','Project Two','Project Three'];
constructor(public fb: FormBuilder) {
this.TicketForm = fb.group({
name: [null, Validators.required],
email: [null, Validators.required],
phone: [null, Validators.required],
ticketID: [null, Validators.compose([Validators.required, ValidationService.numberValidation])],
});
}
submit(form:any, isValid:boolean) {
console.log(form, isValid);
}
}
Validation.service.ts
import { Injectable } from '#angular/core';
import { AbstractControl } from "#angular/forms";
interface ValidationResult {
[key:string]:boolean;
}
#Injectable()
export class ValidationService {
constructor() {}
public static numberValidation(control:AbstractControl): ValidationResult {
return ({'valid':!isNaN(control.value)});
}
}
Check this link with the Custom Form Validation part. Based my answer on that.
as jonrsharpe mentioned, null, means that form is valid. Therefore we return null if form is valid, otherwise we return { “numberValidation”: true }
excerpt from link I provided, customized to your example:
One weird thing you might notice is that returning null actually means the validation is valid. If we find a letter we return the validation error { “numberValidation”: true }
So change your validation to something like this:
#Injectable()
export class ValidationService {
constructor() {}
public static numberValidation(control: AbstractControl): ValidationResult {
if (isNaN(control.value)) {
return { "numberValidation": true }
}
return null;
}
}
and it should work! :)