Cross Field Validation
There are a lot of terms to describe this use case, cross-field validation is when a validation rule needs to use another field's value to validate the current field.
Consider a password
field and a confirmation
field, both fields must match each other but unlike the validation scenarios you've encountered, you now have a dependency between two fields, as one must match the other.
Targeting other fields
vee-validate handles such rules but you must do the following:
- Wrap the fields within the same
ValidationObserver
component. - The target field must have a
name
orvid
prop. - Properly reference the target field
name
orvid
value in the rules of the other.
This can be confusing at first, you can start by defining those two fields:
<ValidationProvider rules="required" v-slot="{ errors }">
<input type="password" v-model="password">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<!-- You can use vid or name to specify a targetable field -->
<ValidationProvider name="confirm" vid="confirm" rules="required" v-slot="{ errors }">
<input type="password" v-model="confirmation">
<span>{{ errors[0] }}</span>
</ValidationProvider>
First you need to wrap them both in a ValidationObserver
component:
<ValidationObserver>
<ValidationProvider rules="required" v-slot="{ errors }">
<input type="password" v-model="password">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<ValidationProvider name="confirm" rules="required" v-slot="{ errors }">
<input type="password" v-model="confirmation">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
The ValidationObserver
not only aggregates the fields state, it also allows them to discover each other and be able to reference one another.
Secondly, create the password
rule, which needs to check if the field value matches the given target
value:
import { extend } from 'vee-validate';
extend('password', {
params: ['target'],
validate(value, { target }) {
return value === target;
},
message: 'Password confirmation does not match'
});
If you test the rule right away like this:
<ValidationProvider rules="required|password:confirm" v-slot="{ errors }">
<input type="password" v-model="password">
<span>{{ errors[0] }}</span>
</ValidationProvider>
You will notice that it doesn't work. This is because vee-validate does not know you are trying to reference another field's value, so the param value is fixed to always be "confirm".
To reference another field's value, add a @
at the beginning of a param to signal to vee-validate that it should substitute the param with the target field value. So instead of getting a static "confirm" string, you will get the field's value instead.
<ValidationObserver>
<ValidationProvider rules="required|password:@confirm" v-slot="{ errors }">
<input type="password" v-model="password">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<ValidationProvider name="confirm" rules="required" v-slot="{ errors }">
<input type="password" v-model="confirmation">
<span>{{ errors[0] }}</span>
</ValidationProvider>
</ValidationObserver>
You can see the previous sample working here:
TIP
Note that the password
rule you created is not special in any way, it just checks if two strings are the same. You can use the @
to reference fields values in any rule you create, there is no limitation on that.
Targeting multiple fields
You are not limited to targeting just 1 field, you can target as many as you like provided that you use the proper syntax to reference that field.
Try another example that's slightly more complex to solidify this concept. Imagine having a field whose value must be between two other fields, our between
rule can be as simple as this:
import { extend } from 'vee-validate';
extend('between', {
params: ['min', 'max'],
validate(value, { min, max }) {
return value >= min && value <= max;
},
message: 'This field value must be between {min} and {max}'
});
Again, make sure to wrap our Providers within the same ValidationObserver
so they can reference each other, and make sure you reference the two fields with @max
and @min
.
Target Fields Names
When generating messages for target fields, it is more useful to have their names rather than their values. For example, a password confirmation rule will not typically say the following:
Your Password does not match the $P455W0RD
It would make more sense to have the target field name instead of its value:
Your Password does not match the Password Confirmation
VeeValidate does that for you automatically when it interpolates the given message, instead of giving you the target field value, it will give you its display name. This is an example of a rule that uses the placeholder for the target
param to display the other field's name.
The same applies to all arguments passed to rules, when a target field is specified, the param placeholder will resolve to the target field name rather than its value. These are a few examples to help make this clear:
Using a
between
rule message that looks like this:
The field must be between {min} and {max}
Assuming you have also these fields:
<!-- Check the table below for the possible between values ->
<ValidationProvider name="value" rules="between:..." v-slot="{ errors }">
<input type="number" v-model="value">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<ValidationProvider vid="maxValue" name="Min Value" rules="required" v-slot="{ errors }">
<input type="number" v-model="min">
<span>{{ errors[0] }}</span>
</ValidationProvider>
<ValidationProvider vid="minValue" name="Max Value" rules="required" v-slot="{ errors }">
<input type="number" v-model="max">
<span>{{ errors[0] }}</span>
</ValidationProvider>
The following is a table that shows the between
rule message in various configurations when coupled with cross-field validation:
between rule arguments | Generated message | Explanation |
---|---|---|
between:3,5 | The field must be between 3 and 5 | Arguments are specified as values are interpolated as is in the message. |
(3, @maxValue) | The field must be between 3 and Max Value | The min argument is interpolated as is, while the max will be the target field name. |
(@minValue, @maxValue) | The field must between Min Value and Max Value | Both arguments will be interpolated as their corresponding fields display names |
If you always need the values of target fields in your message, wrap the argument name with _
like this:
The field must be between {_min_} and {_max_}
Which will always interpolate the values regardless if cross-field validation is used or not.