TL;DR
We are building the documentation of@myndpm/dyn-forms
at mynd.dev and we've added support for a variety of custom functions like Validators, AsyncValidators, Matchers, Conditions and more.
The next crucial part of any form is validation, aka Validators
and AsyncValidators
, and we took some time to study a nice way to implement them and we picked the most declarative one:
createMatConfig('INPUT', {
name: 'quantity',
validators: ['required', ['min', 1] ],
asyncValidators: ['myAsyncValidator'],
Angular Validators
Angular provides default Validators that we're used to consume programatically in our Reactive Forms, some of them are Validator Functions (ValidatorFn
) like Validators.required
, and some others are Validator Factories ((args) => ValidatorFn
) which builds a Validator based on a required parameter like Validators.minLength(4)
.
The definition of a Validator Function is:
(control: AbstractControl) => ValidationErrors | null
it receives the control to be validated, and returns null
if its value is valid, or an error object of the form { [error: string]: any }
Validator Factories are high-order functions that builds a Validator Function according some input parameters:
function minLength(minLength: number): ValidatorFn {
return (control: AbstractControl) => {
return (control.value && control.value.length < minLength)
? { minLength: true } // invalid
: null; // valid
}
}
as you can see, this is a very nice way to parametrize our Functions, so we defined the provisioning of Validators (and all the other handlers) with an id
and a factory fn
:
export interface DynControlValidator {
id: string;
fn: (...args: any[]) => ValidatorFn;
}
The id
will be the string that we will use in our Configuration Object. By default, @myndpm/dyn-forms
provide the default Angular Validators with the same name as we know them: required
, requiredTrue
, email
, pattern
, minLength
, maxLength
, min
and max
.
The notation to use them in the Config Object is as follows:
// without parameters
validators: ['required'],
// with parameters as array
validators: ['required', ['min', 1] ],
// with parameters as object
validators: { required: null, minLength: 4 },
// with an inline ValidatorFn or ValidatorFn factory
validators: [myValidatorFn, myValidatorFactory(args)],
supporting these different notations is unexpensive and can be useful for different kind of systems or developer tastes.
Custom Validators
You can provide inline functions to build a fast prototype, but to store a plain config somewhere, you need to provide your ValidatorFn
Factory with an id
and a fn
in the respective module with a code like this:
import { AbstractControl, ValidatorFn } from '@angular/forms';
import { DynFormsModule } from '@myndpm/dyn-forms';
import { DynControlValidator } from '@myndpm/dyn-forms/core';
const validators: DynControlValidator[] = [
{
id: 'email',
fn: (): ValidatorFn => {
return (control: AbstractControl) => {
// implement my validator
// to return { email: true } | null;
}
}
}
];
@NgModule({
imports: [
DynFormsModule.forFeature({ validators, priority: 100 });
note the priority
parameter to override the default validators (which weight is 0); we will play with priorities in a further article.
AsyncValidators
Providing async validators works in the same way. You provide your fn
with an id
and use them in the Config Object:
createMatConfig('INPUT', {
name: 'quantity',
validators: ['required'],
asyncValidators: ['myAsyncValidatorId'],
and if you need to provide arguments to your AsyncValidator factory, you can use:
// single argument which can be an object
asyncValidators: [['myAsyncValidatorId', args]],
// your factory will receive fn(args)
// multiple arguments in array to be destructured
asyncValidators: [['myAsyncValidatorId', [arg1, arg2]]],
// your factory will receive fn(arg1, arg2)
Custom Handlers
With this notation we added support for multiple kinds of functions that we require in the Dynamic Forms: Validators
and AsyncValidators
as we just saw, Matchers
and Conditions
to manipulate the controls under some special requirements, and also ParamFns
to inject functions to the parameters of the DynControls too.
We will be digging into the conditional executions in the next chapter.
In the meantime, what do you think of this notation?
// PS. We are hiring!
Top comments (7)
Hey, thanks for the great article.
My question is can we apply validators to the
FormGroup
e.g.Yes, every DynControl is able to have its Validators but in the Config type structure:
I'm improving the typing of the Config right now to detect inline
ValidatorFn
in case we don't want to provide them viaid
and just want to build a fast prototype :)Liked it! Thanks for this!
Have a few questions here:
I. What if I wanna to provide a few params for one validator in array notation?
Will this be correct (range ex.):
options: { validators: ['required', ['range', [1, 5] ] ] },
II. For custom validator, if we have defined it in separate const, do we have some way to read value from other form controls somehow? Let's say I wanna validate current control value against another control value from this form. Or in that case we probably should declare custom validator in dyn form host component?
Important point: before reading this article it's nice to read this article first dev.to/myndpm/a-new-approach-to-ha...
I. Yes,
['range', [1, 5]]
will be translated to its corresponding Factory call likefn(1, 5)
and for more complex cases an object could be used like['asyncUniqueEmail', { endpoint: '...', minLength: 4, ... }]
and you will receive it in your Factoryfn({ endpoint, minLength, ... })
:)II. Let's try to implement that Validator with
Matchers
for the next chapter of this series: github.com/myndpm/open-source/issu...Thank you for your feedback! :D
Is it required to register validators on the module level? Does this approach has advantages over providing validators inline when creating config? e.g. when doing
createMatConfig()
while implementing
matchers
it was convenient to have inline functions, and in general they are nicer to implement a fast prototype without having to provide anything at module level.I might update this article to include the inline functions in the notation. Thanks!
great work, I am trying to load all the form control from a JSON file. The below code
controls I need to fetch from a JSON(infuture may be mongoDB). I am not sure if there is a better approach, any suggestions highly appreciated.