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;
}
}
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
HI I need to change print in front screen of user.
Example if is number 900 I need to print in screen 9...
or if values of input form which I got from backend 10 I need to print 1... or whatever..
<input class="select" type="text" formControlName="preparationTime">
this.form = this.formBuilder.group({
preparationTime: ['']
});
Write custom validator or pipe. This is example of pipe:
import { Pipe, PipeTransform } from '#angular/core';
#Pipe({ name: 'reverse' })
export class ReversePipe implements PipeTransform {
transform(value) {
let res = value.slice().reverse();
return res;
}
}
value - is value what you want to change;
res - result after manipulating with value;
This is example of custom validator:
function AgeValidator(control: AbstractControl): { [key: string]: boolean } | null {
if (control.value > 18) {
return { 'age': true };
}
return null;
}
Don't forget to add his to form element:
this.form = this.formBuilder.group({
preparationTime: ['',[AgeValidator]]
});
I've a simple angular2 reactive form:
formangular.component.html
<form novalidate (ngSubmit)="onSubmit(formDir.value)" [formGroup]="complexForm" #formDir="ngForm">
<div class="form-group">
<custom-input id="idCausaleRitenuta"
class="form-control"
label="Codice:"
formControlName="idCausaleRitenuta"
[(ngModel)]="idCausaleRitenuta"></custom-input>
<div *ngIf="idCausaleRitenuta.invalid && (idCausaleRitenuta.dirty || idCausaleRitenuta.touched)"
class="alert alert-danger">
........
the relative .ts file:
formangular.component.ts
export class FormAngularComponent {
get idCausaleRitenuta() { return this.complexForm.get('idCausaleRitenuta');}
constructor(private fb: FormBuilder, private ritenuteService: RitenuteService, private sharedService: SharedService) {
this.createForm();}
createForm() {
this.complexForm = this.fb.group({
idCausaleRitenuta: ['', Validators.compose([Validators.required, Validators.maxLength(4)])],
........
...And the custom-input control:
custominput.component.ts
.................
#Component({
selector: 'custom-input',
template: `<div>
<label *ngIf="label" [attr.for]="identifier">{{label}}</label>
<input [id]="identifier"
[type]="type"
[class]="class"
[placeholder]="placeholder"
[(ngModel)]="value"
(blur)="touch()" />
</div>`,
.......
})
export class CustomInputComponent<T> implements ControlValueAccessor {
.........
identifier = `custom-input-${identifier++}`;
private innerValue: T;
private changed = new Array<(value: T) => void>();
private touched = new Array<() => void>();
get value(): T {
return this.innerValue;
}
set value(value: T) {
if (this.innerValue !== value) {
this.innerValue = value;
this.changed.forEach(f => f(value));
}
}
touch() {
this.touched.forEach(f => f());
}
writeValue(value: T) {
this.innerValue = value;
}
registerOnChange(fn: (value: T) => void) {
this.changed.push(fn);
}
registerOnTouched(fn: () => void) {
this.touched.push(fn);
}
}
let identifier = 0;
When I render the form, the value of the input inside the 'custom control' is '[object Object]', can someone tell me why?
When you make this call:
get idCausaleRitenuta() { return this.complexForm.get('idCausaleRitenuta');}
You are actually getting the FormControl object (not the value of the form control).
You can access the value property of that control like this:
get idCausaleRitenuta() { return this.complexForm.get('idCausaleRitenuta').value;}
I'm not sure if there's a better way to do this, I assume there should be. Basically I have a component I want to treat as a standalone form control. This control will always have some sort of special validation attached and I would like it to bubble up to the form whenever the component is used.
I've attached a plunker. Is there a way to for the form to be marked invalid if the component/formControl is invalid? I know I could add the validator to the form itself, but I would like to make things easy and more predictable for future use of this component. I'm also open to better ideas of doing this.
#Component({
selector: 'my-app',
template: `
<form [formGroup]="form">
<my-component-control formControlName="myComponent"></my-component-control>
</form>
<div>Form Value: {{form.value | json}}</div>
<div>Form Valid: {{form.valid}}</div>
`,
})
export class App {
constructor(fb: FormBuilder) {
this.form = fb.group({
myComponent: ''
});
}
}
#Component({
selector: 'my-component-control',
template: `
<div>Control Errors: {{control.errors | json}}</div>
<input [formControl]="control">
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => MyComponentComponent),
}
]
})
export class MyComponentComponent implements ControlValueAccessor {
control: FormControl;
onChange: any = () => { };
onTouched: any = () => { };
constructor() {
this.control = new FormControl('', (control) => {
return control.value ? null : {shouldHaveSomething: true};
});
this.control.valueChanges.subscribe((value) => {
this.onChange(value);
this.onTouched();
});
}
writeValue (obj: any): void {
this.control.setValue(obj);
}
registerOnChange (fn: any): void {
this.onChange = fn;
}
registerOnTouched (fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
throw new Error('Method not implemented.');
}
}
One solution could be using setValidators method on host AbstractControl
To do so I'm going to get reference to AbstractControl through NgControl. We can't just inject NgControl in constructor because we'll get issue with instantiating cyclic dependency.
constructor(private injector: Injector) {
...
}
ngOnInit() {
Promise.resolve().then(() => {
const hostControl = this.injector.get(NgControl, null);
if (hostControl) {
hostControl.control.setValidators(this.control.validator);
hostControl.control.updateValueAndValidity();
}
});
}
Ng-run Example