Reputation: 12433
I'm trying to make a custom validator for my FormControl mealType
If my FormControl category
has a value and mealType
does not, mealType
should be invalid.
If category
has no value, mealType
should be valid.
I'm getting a console error:
TypeError: Cannot read property 'get' of undefined
code:
ngOnInit() {
this.findForm = this.formBuilder.group({
categories: [null, Validators.required],
mealTypes: [null, this.validateMealType],
distanceNumber: null,
distanceUnit: 'kilometers',
keywords: null,
});
}
validateMealType() {
if (this.findForm.get('categories').value) {
if (this.findForm.get('mealTypes').value) {
var mealTypeError = false;
} else {
var mealTypeError = true;
}
} else {
var mealTypeError = false;
}
return mealTypeError ? null : {
error: true
}
}
It is my form that is undefined.
How do I resolve this?
Trying this:
validateMealType(categoryControl: FormControl, mealTypeControl: FormControl) {
if (categoryControl.value) {
if (!mealTypeControl.value) {
var mealTypeError = true;
} else {
var mealTypeError = false;
}
} else {
var mealTypeError = false;
}
return mealTypeError ? null : {
error: true
}
}
but it causes:
Error in app/find-page/subcomponents/find-page/find-form.component.html:36:5 caused by: Cannot read property 'value' of undefined
trying this:
class MealTypeValidator {
constructor(private categoryFormControl: FormControl) { }
mealTypeValidator(control: FormControl): { [error: string]: any } {
if (this.categoryFormControl.value) {
if (!control.value) {
return { error: true };
}
}
}
}
then in my form component:
ngOnInit() {
this.findForm = this.formBuilder.group({
categories: [null, Validators.required],
mealTypes: [null, new MealTypeValidator(this.findForm.get('categories').mealTypeValidator()],
distanceNumber: null,
distanceUnit: 'kilometers',
keywords: null,
});
}
but I have compilation errors. How do I get this right? I think i'm just a bit off on both the validation class I made and the usage of it.
Upvotes: 28
Views: 24623
Reputation: 2247
You can navigate your way through the control and its parent form group to another control:
example(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const forbidden = control.value < control.parent.controls["anotherControl"].value;
return forbidden ? { forbidden: { message: "Custom message" } } : null;
};
}
Add the above as a function inside your component and declare this validator to your form control:
formGroup = new FormGroup({
//..
targetControl: new FormControl("", [this.example()]),
anotherControl: new FormControl("")
//..
});
Notes: The proper way of doing cross-field validation is by using a custom validator at the form group level. In that case, the generated errors would also appear on that same level. Now, if you want a specific control to be assigned the error, you can use my suggestion above but you need to keep in mind that you would have to manually update the value and validity of each interested control every time their value changes if you are using the default "update on change" strategy. (Because the validity of a control might change once you update the dependent control and angular only updates the control whose value changed)
Upvotes: 12
Reputation: 8119
mealTypes: [null, new MealTypeValidator(this.findForm.get('categories').mealTypeValidator()]
But we can get parent from control and using optional operator we can reference to value and get another control value then compare whatever logic we need.
ngOnInit() {
this.findForm = this.formBuilder.group({
categories: [null, Validators.required],
mealTypes: [null, this.mealTypeValidator()],
distanceNumber: null,
distanceUnit: 'kilometers',
keywords: null,
});
}
mealTypeValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return control.parent?.value.categories && !control.value
? { forbidden: { message: 'MealType should have a type under category!' } }
: null;
};
}
Upvotes: 3
Reputation: 2367
The solution proposed by @Michael worked for me with a minor change for the Angular 4.
In the validation function, I needed to change the parameter type from AbstractControl to FormGroup because the AbstractControl in this version does not contain the controls collection.
function validateEqual(form: FormGroup): { [key: string]: boolean } {
const senha = form.controls['novaSenha'];
const confirmacaoSenha = form.controls['confirmacaoNovaSenha'];
if (senha != undefined && confirmacaoSenha != undefined) {
const senhaValue = senha.value;
const confirmacaoSenhaValue = confirmacaoSenha.value;
if (senhaValue !== confirmacaoSenhaValue) {
return { 'A senha e a confirmação não coincidem.': true};
}
return null;
}
}
Thanks too, @Ariel who founds this post.
Upvotes: 5
Reputation: 1726
You are one step closer.
You need to attach your custom validator to the FormGroup
instead, because it needs to know two FormControl
(categories
and mealTypes
), so attaching to FormGroup
will give the validator more broad view and access to the entire FormControl
To achieve that, change your ngOnInit
to
ngOnInit() {
this.findForm = new FormGroup({
mealTypes : new FormControl(null, Validators.Required),
categories : new FormControl(null)
// others form control here
}, validateMealType); // <-- see here is your custom function
}
On above code, you actually have to use FormGroup
constructor instead of FormBuilder
, so you can attach your custom validation in the parameters. Also, move your custom validator outside the component class.
Take a look at this Plunker to get more insight for your specific case here.
Upvotes: 25