Remote validation of complex forms with angularDavid IblBlockedUnblockFollowFollowingMar 19Since duplication of business rules is the top most critical smell when implementing any application, validation has to be done server side without duplicated validation rules on the client.
Further, from the point of view of user experience, the validation feedback should be immediate.
And last but not least, in an ideal world, validation rules are transparent and readable for those who define them originally.
Remote validationDigital services are mission-critical components of the sales process for many businesses.
The creation and shipment of a new individual or special service-based product is depending on the simplicity of service change and the ease of client development.
While the integration of services in different clients and platforms is essential to extend the reach of any product, it is totally necessary to keep full control of the business rules within the provided services.
Since validation rules and complex business object validation is the core component of every service based product, the centralization of the validation ruleset is critical when we want to change our product quickly.
In a modern application architecture with services and clients, the service is the one and only point where validation cannot be removed in any case.
The duplicated validation rules are on the client.
We can only solve this problem by removing validation rules on the client side.
This means not to remove validation on the client side.
Only the definition of the validation rules should not be duplicated in the client application.
During this article, we will see that this strategy will increase technical complexity on the client.
The main advantage, the reduced complexity from a business point of view will still lead to faster and easier client development.
The target will be to move validation to the server without affecting user experience.
As the second key result, we wanted to express the complex validation of business objects, not only simple form field validation.
Defining some kind of validation response modelFirst of all, we have to define a response model which should be able to transport any validation result.
We decided to express validation errors as a list of key-value pairs, assigning fields to error messages.
To identify fields in complex, nested object trees we defined that the key can contain path information.
Therefore the key can contain a string defining a field name like “name” or a field path like “address.
A model to describe the validation errorsThe response to a validation request could look like this:validation errors response2.
Adding a remote validatorLet’s start the engine.
The boilerplate of our validation directive is as easy as any control validator.
The only difference is the type of the expected parameter in the validation method signature.
We will expect a FormGroup there rather than a FormControl.
There are two possible implementations, each with its own limitations in concerns of user experience.
We could provide our validator as an async validation directive.
This will lead to a special behaviour of angular itself.
All async validator will only reevaluate when fields with former errors change.
Since we are validating complex objects with dependent validations, the outcome of this strategy may be curious.
The second implementation handles the whole async thing itself.
Let’s face some code.
A first implementation of the form validatorThe form decorated with the form validator.
When the form changes we send the current value to a validation endpoint and get validation errors back.
In a real-world implementation, we could pass the correct validation endpoint as an input parameter, or provide a concrete implementation of the service on component level.
Providing the endpointAfter the callback is triggered we update the errors attribute of any form control matching the errors field name or field path.
With some reducer help, we even traverse the controls and form-group tree to find nested fields and assign nested field errors correctly.
Utilizing the path to find nested form controls.
As you have seen above we have to clear any error on our form-controls right before assigning the current errors to ensure a consistent state without providing the valid state of each form field.
And never forget to manage subscription.
We take care of unsubscribing in the ngOnDestroy hook.
This base implementation already provides a great result.
The form state gets validated dynamically against a server-side defined ruleset.
Since the controls show their errors right after they become touched the experience is like using client-side validation.
Cosmetics…Now, just some minor cosmetics have to be done to provide a first class experience.
First of all, validation gets triggered by every keystroke.
And especially on initialisation, the validator sends a request after each field is rendered.
The best solution up here was to debounce each change with the help of a subject.
embedding debouncingThe code above shows a simple solution to prevent immediate validation on every keystroke without switching to validation on blur.
As a result, we reduce the validation frequency between client and server.
We initialize a ReplaySubject and fire each validation request into it.
During initialization, we subscribe to the subject and add some debounceTime right before calling the action.
Since we always return null at the end of the validation method we undermine the form state management of angular.
A solution to meet all requirements is to set all controls to a pending state before calling the server-side validation endpoint.
Mark every control as pending.
Angular will manage the state again when setting the errors object of each control.
The result is, that the form and each control are marked as pending right before the remote API call, and with the correct valid state right after the API call.
Binding objects to the form?One more thing…During implementation, we asked ourselves on how to bind a complex object directly to the form without the declaration of each field-binding.
At least, we found a simple solution by implementing a directive bidirectional binding the form value.
Double bind a complex object to a form.
Corresponding HTMLAs you see, the formModelDirective provides an input and an output property to take and provide a value.
When a value is bound in, the value of the form-group directive gets updated.
If the form changes its value, the output emits the new value.
Angular will match all controls within the form by comparing the name property of each form-control.
With the help of the ngModelGroup directive, it is even possible to navigate through a nested object tree.
The only disadvantage of this implementation is that nested objects never become null.
From our point of view a negligible trade-off.
The ngModel property of each control is used to set a default value.
Data already present is bound by the formModel directive to any form.
And since we have a two-way bound object within the component, we don’t have any trade-offs in concerns of working with the object or its values.
Last thoughts and creditsFirst, many thanks to my team and especially Thinh Van Le for the first base implementation of this.
Great work, guys.
🙂 And secondly, many thanks to Thomas Billenkamp for proofreading and validating the code.
The main opportunity is the ability to provide validation to different clients in a convenient way.
In an API driven world centralised rules and validation engines become the enabler when we think about the distribution and provisioning of custom services.
One of my next articles will show a very expressional way to describe validation rules using the DMN.
Feel free to share your impressions or provide hints to improve this solution.
Find a GitHub repository containing the full implementation here:davidibl/form-validationContribute to davidibl/form-validation development by creating an account on GitHub.
comThis implementation is based on the controls you may find here:davidibl/web-componentsAngular web components library.
Contribute to davidibl/web-components development by creating an account on GitHub.
comSince the only regardable relation is the form validation errors are shown in this component library it can be used with nearly every component library or forms implementation.
Find an article describing extended validation messages here:Advanced reusable custom Angular validatorModern usability concepts require advanced validation technicsmedium.