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
Related
I want to add a custom unique validator that will validate that all label fields values are unique.
(I) When I change the form values, the value of this.form changes after it is passed in CustomValidator.uniqueValidator(this.form). How to fix this?
(II) Is there any way of doing this without using any package?
Note: Forms have default values on load. Here is the screenshot.
this.form = this.fb.group({
fields: this.fb.array([])
});
private addFields(fieldControl?) {
return this.fb.group({
label: [
{value: fieldControl ? fieldControl.label : '', disabled: this.makeComponentReadOnly}, [
Validators.maxLength(30), CustomValidator.uniqueValidator(this.form)
]],
isRequired: [
{value: fieldControl ? fieldControl.isRequired : false, disabled: this.makeComponentReadOnly}],
type: [fieldControl ? fieldControl.type : 'text']
});
}
static uniqueValidator(form: any): ValidatorFn | null {
return (control: AbstractControl): ValidationErrors | null => {
console.log('control..: ', control);
const name = control.value;
if (form.value.fields.filter(v => v.label.toLowerCase() === control.value.toLowerCase()).length > 1) {
return {
notUnique: control.value
};
} else {
return null;
}
};
}
in real life, username or email properties are checked to be unique. This will be very long answer I hope you can follow along. I will show how to check uniqueness of username.
to check the database, you have to create a service to make a request. so this validator will be async validator and it will be written in class. this class will be communicate with the service via the dependency injection technique.
First thing you need to setup HttpClientModule. in app.module.ts
import { HttpClientModule } from '#angular/common/http';
#NgModule({
declarations: [AppComponent],
imports: [BrowserModule, YourOthersModule , HttpClientModule],
providers: [],
bootstrap: [AppComponent],
})
then create a service
ng g service Auth //named it Auth
in this auth.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private http: HttpClient) {}
userNameAvailable(username: string) {
// avoid type "any". check the response obj and put a clear type
return this.http.post<any>('https://api.angular.com/username', {
username:username,
});
}
}
now create a class ng g class UniqueUsername and in this class:
import { Injectable } from '#angular/core';
import { AsyncValidator, FormControl } from '#angular/forms';
import { map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import { AuthService } from './auth.service';
// this class needs to use the dependency injection to reach the http client to make an api request
// we can only access to http client with dependecny injection system
// now we need to decorate this class with Injectable to access to AuthService
#Injectable({
providedIn: 'root',
})
export class UniqueUsername implements AsyncValidator {
constructor(private authService: AuthService) {}
//this will be used by the usernamae FormControl
//we use arrow function cause this function will be called by a
//different context, but we want it to have this class' context
//because this method needs to reach `this.authService`. in other context `this.authService` will be undefined.
// if this validator would be used by the FormGroup, you could use
"FormGroup" type.
//if you are not sure you can use type "control: AbstractControl"
//In this case you use it for a FormControl
validate = (control: FormControl) => {
const { value } = control;
return this.authService.userNameAvailable(value).pipe(
//errors skip the map(). if we return null, means we got 200 response code, our request will indicate that username is available
//catchError will catch the error
map(() => {
return null;
}),
catchError((err) => {
console.log(err);
//you have to console the error to see what the error object is. so u can
// set up your logic based on properties of the error object.
// i set as err.error.username as an example. your api server might return an error object with different properties.
if (err.error.username) {
//catchError has to return a new Observable and "of" is a shortcut
//if err.error.username exists, i will attach `{ nonUniqueUsername: true }` to the formControl's error object.
return of({ nonUniqueUsername: true });
}
return of({ noConnection: true });
})
);
};
}
So far we handled the service and async class validator, now we implement this on the form. I ll have only username field.
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, Validators } from '#angular/forms';
import { UniqueUsername } from '../validators/unique-username';
#Component({
selector: 'app-signup',
templateUrl: './signup.component.html',
styleUrls: ['./signup.component.css'],
})
export class SignupComponent implements OnInit {
authForm = new FormGroup(
{
// async validators are the third arg
username: new FormControl(
'',
[
Validators.required,
Validators.minLength(3),
Validators.maxLength(20),
Validators.pattern(/^[a-z0-9]+$/),
],
// async validators are gonna run after all sync validators
successfully completed running because async operations are
expensive.
this.uniqueUsername.validate
),
},
{ validators: [this.matchPassword.validate] }
);
constructor(
private uniqueUsername: UniqueUsername
) {}
//this is used inside the template file. you will see down below
showErrors() {
const { dirty, touched, errors } = this.control;
return dirty && touched && errors;
}
ngOnInit(): void {}
}
Final step is to show the error to the user: in the form component's template file:
<div class="field">
<input formControl="username" />
<!-- this is where you show the error to the client -->
<!-- showErrors() is a method inside the class -->
<div *ngIf="showErrors()" class="ui pointing red basic label">
<!-- authForm.get('username') you access to the "username" formControl -->
<p *ngIf="authForm.get('username').errors.required">Value is required</p>
<p *ngIf="authForm.get('username').errors.minlength">
Value must be longer
{{ authForm.get('username').errors.minlength.actualLength }} characters
</p>
<p *ngIf="authForm.get('username').errors.maxlength">
Value must be less than {{ authForm.get('username').errors.maxlength.requiredLength }}
</p>
<p *ngIf="authForm.get('username').errors.nonUniqueUsername">Username is taken</p>
<p *ngIf="authForm.get('username').errors.noConnection">Can't tell if username is taken</p>
</div>
</div>
You could create a validator directive that goes on the parent element (an ngModelGroup or the form itself):
import { Directive } from '#angular/core';
import { FormGroup, ValidationErrors, Validator, NG_VALIDATORS } from '#angular/forms';
#Directive({
selector: '[validate-uniqueness]',
providers: [{ provide: NG_VALIDATORS, useExisting: UniquenessValidator, multi: true }]
})
export class UniquenessValidator implements Validator {
validate(formGroup: FormGroup): ValidationErrors | null {
let firstControl = formGroup.controls['first']
let secondControl = formgroup.controls['second']
// If you need to reach outside current group use this syntax:
let thirdControl = (<FormGroup>formGroup.root).controls['third']
// Then validate whatever you want to validate
// To check if they are present and unique:
if ((firstControl && firstControl.value) &&
(secondControl && secondControl.value) &&
(thirdContreol && thirdControl.value) &&
(firstControl.value != secondControl.value) &&
(secondControl.value != thirdControl.value) &&
(thirdControl.value != firstControl.value)) {
return null;
}
return { validateUniqueness: false }
}
}
You can probably simplify that check, but I think you get the point.
I didn't test this code, but I recently did something similar with just 2 fields in this project if you want to take a look:
https://github.com/H3AR7B3A7/EarlyAngularProjects/blob/master/modelForms/src/app/advanced-form/validate-about-or-image.directive.ts
Needless to say, custom validators like this are fairly business specific and hard to make reusable in most cases. Change to the form might need change to the directive. There is other ways to do this, but this does work and it is a fairly simple option.
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.
]))
})
}
}
I wrote a custom validator for Reactive forms in Angular 2.But my function is validating only first key press in the text field. Here my custom function supposed to validate each key press. Could any one please correct me.
This is the way I am calling custom function in my class.
'customerNumberOwner': new FormControl('', [CustomValidators.CustomerNumberCustomValidation(6,8)]),
Here is my custom function.
//Custom validator for Customer number owner
static CustomerNumberCustomValidation(min: number, max: number): ValidatorFn {
return (c: AbstractControl): { [key: string]: boolean } | null => {
var reg=/[^A-Za-z0-9]+/;
if(c && (c.value !== '')){
const str=c.value;
if (str.match(reg) || str.length<min ||str.length>max ){
console.log('Invalid')
return {
'CustomerNumberCustomValidation' : true
};
}
}
return null;
};
}
I hope this will help
DEMO
HTML:
<h1>
Try Reactive Form Validation with custom validation
</h1>
<form [formGroup]="basicForm">
<input type="text" minlength="10" maxlength="10" formControlName="name" placeholder="Enter Name For Validation" />
<p *ngIf="basicForm.get('name').hasError('required') && basicForm.get('name').touched">Required</p>
<p *ngIf="basicForm.get('name').hasError('minlength')">Min Length 10</p>
<p *ngIf="basicForm.get('name').hasError('maxlength')">Max Length 10</p>
<p *ngIf="basicForm.get('name').hasError('CustomerNumberCustomValidation')">Pattern Invalid /[^A-Za-z0-9]+/</p>
</form>
app.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { CustomValidatorService } from './custom-validator.service'
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
basicForm: FormGroup;
ngOnInit() {
this.createForm();
}
constructor(private fb: FormBuilder) {
}
createForm() {
this.basicForm = this.fb.group({
name: this.fb.control(null, [Validators.required, Validators.minLength(10), CustomValidatorService.CustomerNumberCustomValidation])
})
}
}
custom-validator.service.ts:
import { Injectable } from '#angular/core';
import { AbstractControl, FormControl, ValidatorFn } from '#angular/forms';
#Injectable()
export class CustomValidatorService {
constructor() { }
static CustomerNumberCustomValidation(control: FormControl) {
var reg = /[^A-Za-z0-9]+/;
if (control.value) {
const matches = control.value.match(reg);
return matches ? null : { 'CustomerNumberCustomValidation': true };
} else {
return null;
}
}
}
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;
}
}
Problem
I am trying to add functionality to the built-in NgForm directive by intercepting the onSubmit function in order to prevent double-submission and invalid submission, but I haven't been able to find a way to do so without monkey patching.
Failed Attempt 1: Decorator via Dependency Injection
I didn't really expect this to work with directives since they aren't really "providers", but I tried it anyway (to no avail).
import { Injectable } from '#angular/core';
import { NgForm } from '#angular/forms';
#Injectable()
export class NgFormDecorator extends NgForm {
constructor() {
super(null, null);
}
onSubmit($event: Event): boolean {
// TODO: Prevent submission if in progress
return super.onSubmit($event);
}
}
// Module configuration
providers: [{
provide: NgForm,
useClass: NgFormDecorator
}]
Working Attempt 2: Monkey Patch with Secondary Directive
This works great but is obviously not ideal.
import { Directive, Output, EventEmitter } from '#angular/core';
import { NgForm } from '#angular/forms';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/finally';
import { noop } from 'rxjs/util/noop';
#Directive({
selector: 'form',
exportAs: 'NgFormExtension'
})
export class NgFormExtensionDirective {
private onSubmitBase: ($event: Event) => void;
submitting: boolean;
constructor(private ngForm: NgForm) {
this.onSubmitBase = ngForm.onSubmit;
ngForm.onSubmit = this.onSubmit.bind(this);
}
private onSubmit($event: FormSubmitEvent): boolean {
if (this.submitting || this.ngForm.invalid) {
return false;
}
this.submitting = true;
const result = this.onSubmitBase.call(this.ngForm, $event);
if ($event.submission) {
$event.submission
.finally(() => this.submitting = false)
.subscribe(null, noop);
} else {
this.submitting = false;
}
return result;
}
}
export class FormSubmitEvent extends Event {
submission: Observable<any>;
}
Question
Is there a way to decorate/intercept a built-in directive in Angular 4 without monkey patching?
You can always just override the ngForm selector, and extend the NgForm class:
#Directive({
selector: 'form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]',
})
export class CNgFormDirective extends NgForm {
constructor(
#Optional() #Self() #Inject(NG_VALIDATORS) validators: any[],
#Optional() #Self() #Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
super(validators, asyncValidators);
}
onSubmit($event: Event): boolean {
console.log(`I'm custom!`);
return super.onSubmit($event);
}
}
working stack