Defining & Customizing Rules
Creating and customing Aurelia Validation rules to ensure data is validated.
Let us also consider the following Person
class, and we want to define validation rules for this class or instance of this class.
To define rules use the IValidationRules
fluent API. In order to do that you need to inject the IValidationRules
. This is shown in the following example.
The fluent API syntax has following parts.
Start applying ruleset on a target using
.on
. The target can be an object instance or class.Select a property from the target using
.ensure
.Associate rules with the property using
.required
,.matches
etc.Customize the property rules using
.wthMessage
,.when
etc.
Specify validation target using .on
.on
Be it is an object instance or class, both can be used as validation target using .on
.
Specifying the target serves two purposes. Firstly, this initiates an empty collection of rules (ruleset) for the target. Secondly, this helps providing the typing information from the target to the subsequent methods which in turn provides with intellisense for the property names (see next section).
Specifying target property for validation using .ensure
.ensure
The .ensure
method can be use used select a property of the target for validation. This adds an instance of PropertyRule
to the ruleset for the object. The property can be defined using a string or an arrow function expression.
With TypeScript support, intellisense is available for both the variants.
Associating validation rules with property
After selecting a property with .ensure
the next step is to associate rules. The rules can be built-in or custom. Irrespective of what kind of rule it is, at the low-level it is nothing but an instance of the rule class. For example, the "required" validation is implemented by the RequiredRule
class. This will be more clear when you will define custom validation rules. However, let us take a look at the built-in rules first.
required
Considers the property to be valid if the value is not null
, and not undefined
. In case of string, it must not be empty.
This instantiates a RequiredRule
for the property.
Note that this is the only built-in rule that considers
null
,undefined
, or empty string as invalid value. The other built-in rules purposefully considernull
,undefined
, or empty string as valid value. This is done to ensure single responsibility for the built-in rules.
matches
Considers the string property to be valid if the value matches the given pattern described by a regular expression.
This instantiates a RegexRule
for the property.
email
This also instantiates a RegexRule
for the property, but with a specific regex for matching emails.
minLength
Considers the string property to be valid if the value is at least of the specified length. Under the hood, it instantiates a LengthRule
with minimum length constraint.
maxLength
Considers the string property to be valid if the value is at most of the specified length. Under the hood, it instantiates a LengthRule
with maximum length constraint.
minItems
Considers the collection (array) property to be valid if the array has at least the number of items specified by the constraint. Under the hood, it instantiates a SizeRule
with minimum size constraint.
maxItems
Considers the collection (array) property to be valid if the array has at most the number of items specified by the constraint. Under the hood, it instantiates a SizeRule
with maximum size constraint.
min
Considers the numeric property to be valid if the value is greater than or equal to the given lower bound. Under the hood, it instantiates a RangeRule
with [min,]
interval (if your unfamiliar with the interval notation, you can refer this.
max
Considers the numeric property to be valid if the value is less than or equal to the given upper bound. Under the hood, it instantiates a RangeRule
with [,max]
interval (if your unfamiliar with the interval notation, you can refer this.
range
Considers the numeric property to be valid if the value is greater than or equal to the given lower bound and less than or equal to the given upper bound. Under the hood, it instantiates a RangeRule
with [min,max]
interval (if your unfamiliar with the interval notation, you can refer this.
between
Considers the numeric property to be valid if the value is strictly greater than the given lower bound and strictly less than the given upper bound. If the value matches any of the boundary value, it is considered invalid. Under the hood, it instantiates a RangeRule
with (min,max)
interval (if your unfamiliar with the interval notation, you can refer this.
equals
Considers the property to be valid if the value is strictly equal to the expected value. Under the hood, it instantiates a EqualsRule
.
Have you noticed that the same rule implementation is "alias"ed for multiple validation rules? You will get know another aspect of aliasing rules in the customizing validation messages section.
Custom rules
There are two ways custom rules can be defined.
satisfies
This is the easiest way of defining a custom rule using an arrow function that has loosely the following signature.
The value of the first argument provides the value being validated, whereas the value of the second argument provides the containing object. You can use it as follows.
This is useful for the rules that are used only in one place. For example, in one of view-model you need to apply a very specific rule that is not needed elsewhere. However, if you want to reuse you rule, then you need to use the
satisfiesRule
.
satisfiesRule
This lets reuse a rule implementation. For this we need to remember two things. Firstly, as mentioned before at the start of this section any rule can be applied on a property just by instantiating the rule, and associating with the property. Secondly, every rule needs to be a subtype of
BaseValidationRule
, as discussed in before.The method
satisfiesRule
accepts such an instance of a rule implementation and associates it with the property. It can be used as follows.
You must have noticed that the API for the built rules instantiates a rule implementation. For example, the following two are synonymous.
Let us look at one last example before moving to next section. The following example implements a integer range rule by inheriting the RangeRule
.
In the light of using rule instance, note that the the lambda in
satisfies
is actually wrapped in an instance of anonymous subclass ofBaseValidationRule
.
State rule using satisfiesState
Typically a validation rule has two states, valid and invalid. Although it is generally true that a rule has a single valid state, it is possible for a rule to have multiple invalid states. Consider a certificate validation rule. In that case, it is possible to have multiple invalid states, such as "expired", "revoked", "not yet valid", etc. Naturally, we would like to provide a custom message for each invalid state.
This can be done using the .satisfiesState
method. An example will look like as follows.
When using @aurelia/validation-i18n
to localize the messages, the message lookup can have the key of the localized message instead of the message itself.
Defining rules for multiple objects
Rules on multiple objects can be defined by simply using the API in sequence for multiple objects. An example is shown below.
Note that there is no limitation on how many times on
is applied on an object or in what order. The following is a perfectly valid rule definition, although such definition can be difficult to understand.
Customizing rules
In this section you will learn about how to customize the rule definition. A common use-case of that will be to customize the validation messages, as in most of the cases, the default validation messages might not fit the real-life requirements. Apart from that this section also discusses further ways to change the chaining of the rules.
Customize the display name
By default, the display name of the property is computed by splitting on capital letters and capitalizing the first letter of the property name. For example, the property named age
will appear as "Age" in the validation messages for age
, or firstName
becomes "First Name", etc. It is quite evident that this is limiting in a sense. However, the display name of the property can easily be changed using .displayName
.
Note that instead of a literal string, a function can also be used to customize the display name. The function signature is () => string
; The following example shows a use case for this where the attempt for a guessing game is validated, and the attempt count is increased with each attempt and shown in the validation message. Note that some parts of the example is not yet discussed, but those will be addressed in respective sections.
Customize the validation message
Apart from customizing the display name, you can in fact customize the whole message. Messages can be customized on a per-instance basis or globally. Let us first consider the per-instance based customization.
For this, we need to use the withMessage
method. The example below shows how it can be used to define different messages for different rule instances.
The above examples shows usage of string literal as custom message. A message template can also be used instead. The expressions supported in the template are as follows.
$object
: The object being validated. Note that any property of the object can thus be accessed in the template.$value
: The value being validated.$displayName
: The display name of the property.$propertyName
: The name of the property.$rule
: The associated rule instance. This is useful to access the properties of the rule instance. For example, you have a custom validation rule, and you want to access the value of some of your rule property in the validation message, this property is what you need to use. This is used in the messages ofRangeRule
to access the "min", and "max" values of the rule instance.$getDisplayName
: It is a function that returns the display name of another given property. This is useful if you want to create a message associating another property.
Let us look at the following example, to understand these better.
Apart from this the messages the can be customized globally. You must have noted that same rule implementations are aliased quite frequently. The same concept is used here as well.
The messages can be customized globally during registering the plugin, using the CustomMessages
property of the configuration object.
You are encouraged to play with the following demo; define more rules, change the custom messages, etc. to see it in action.
Following is the complete list of default messages for the out of the box validation rules.
RequiredRule
RegexRule
LengthRule
SizeRule
RangeRule
EqualsRule
Note that a new key can also be added to any out of the box rule, and can be referred from the code using withMessageKey
.
If you want to define aliases for your custom rules, you need to decorate the rule class with validationRule
.
Then you can refer the second message by using .withMessageKey('key2')
. Refer the demo below, to see this in action.
Conditional Rule
Often you would want to execute a rule conditionally. This can be done using the .when
method. This method takes a function, with signature (object: any) => boolean
, as input which is later evaluated during rule evaluation to decide whether or not to execute this rule. The object
in the argument of the function is the object being validated.
Sequencing Rules
When multiple rules are defined on a property, the rules are all executed on parallel. Using then
, the rules can be chained and executed serially. This means that if the first rule fails, the second rule is not executed. A common example is shown below.
Assuming there is an implementation of UniqueRule
that validates the data against the records, existing in data store/backend service. Such rules can be expensive in nature, and thus it makes sense to execute those when all other preconditions are validated. In the above example if either of the required
or email
rules fails, the UniqueRule
will never be executed. Verify this in the demo shown below.
Validating object
So far we have seen how to define validation rules on properties. Validation rules can also be applied on an object using ensureObject
, and validate the object as a whole.
Validator and validate instruction
In all the demos so far we have seen usage of validation controller. Under the hood, validation controller uses the IValidator
API to perform the validation.
Loosely the interface looks as follows.
It has only a single method called validate
, which accepts an instruction that describes what needs to be be validated.
The plugin ships a standard implementation of the IValidator
interface. This can be injected to manually perform the validation on objects. Note that validator is the core component that executes the validation rules without any connection with the view. This is the main difference between validator and validation controller. The following example shows how to use it.
An important aspect of the demo above is that it shows how to use
@aurelia/validation
without the@aurelia/validation-html
.
Let us now focus on the ValidateInstruction
, which basically instructs the validator, on what to validate. The instruction can be manipulated using the following optional class properties.
object
: The object to validate.propertyName
: The property name to validate.rules
: The specific rules to execute.objectTag
: When present instructs to validate only specific ruleset defined for a object. Tagging is discussed in detail in the respective sectionpropertyTag
: When present instructs to validate only specific ruleset for a property. Tagging is discussed in detail in the respective section
Some of the useful combinations are as follows.
Note that in the presence of rules
the objectTag
is ignored. However, we strongly encourage the usage of tags for executing specific set of rules. You can find more details on tagging in Tagging rules section. Note that the validate instruction is also respected by validation controller.
Last updated