Reputation: 328
I am trying to create a custom reactive form validation that allows me to pass an array of data to check if a string already exists. I am able to do it one way, so that it would be a form level validation but I can't get it to work on an individual form control.
This will create the error on the whole form, not just that control
this.myForm = this.fb.group({
name: ['', Validators.compose([
Validators.required,
]),
],
}, {
validator: (formGroup: FormGroup) => this.checkStringExists(
formGroup.controls.name,
this.arrayOfStrings,
),
});
Custom validation that allows me to take in the form control and check it against an array that is passed in.
checkStringExists(formInput: AbstractControl, names: string[]): { [s: string]: boolean } {
if (names && names.length && formInput && formInput.value) {
const isUnique = !names.find((name) => name === formInput);
if (isUnique) {
return { nameExists: true };
}
}
return null;
}
This will create the error only on the specific control
this.myForm = this.fb.group({
name: ['', Validators.compose([
Validators.required,
this.checkStringExists(this.arrayOfStrings),
]),
],
});
Custom validation that allows me to take only the array as part of the Validators.compose[] Here
checkStringExists(names: string[]): ValidatorFn {
return (formInput: AbstractControl): ValidationErrors | null => {
if (names && names.length && formInput && formInput.value) {
const isUnique = !names.find((name) => name === formInput);
if (!isUnique) {
return { nameExists: true };
}
}
return null;
};
}
This subscription sets the value for arrayOfStrings.
mySubscription.subscribe((value: string[]) => {
this.arrayOfStrings = value;
})
The problem I have is arrayOfStrings may update multiple times. if I use the validation the first way, the arrayOfStrings is up to date. If I use the validation the second way, arrayOfStrings is null/initial value.
I am trying to get this validation to work the second way, so that I can display validation based on an individual control, not if the whole form has this error. Does anyone know how I can pass this value and keep it up to date?
I also have the validation functions in a separate helper file for reusability across the app.
Upvotes: 1
Views: 4971
Reputation: 707
From you post you seem to have the following wish-list:
The following is copied from your question above:
checkStringExists(formInput: AbstractControl, names: string[]): { [s: string]: boolean } {
if (names && names.length && formInput && formInput.value) {
const isUnique = !names.find((name) => name === formInput);
if (isUnique) {
return { nameExists: true };
}
}
return null;
}
Rather than using this in the validator section of the form you can use it at the field level, like so:
// Contents could change later!
arrayOfStrings: string[] = [];
...
this.individualControlForm = this.fb.group({
name: ['', Validators.compose([
Validators.required,
(control: AbstractControl) => ValidationHelper.checkStringExists(
control, this.arrayOfStrings
)]),
],
});
Because we use an arrow function as our validation function we inherit this
from the scope the function is defined in (paraphrase of the "call, apply and bind" section, here). This (heh) means we can use the array normally with the latest value being used each time the validation function is called.
If you want to reduce the size of this call you can use the .bind(this)
function that Eliseo mentioned in his answer. It can make things harder to read, but certainly shortens the boilerplate of creating your form. Pick your poison.
If you do ever need to use the whole form validation (e.g. if you need to consider multiple fields to decide if a single field is valid) but want the error to show up against the field you can use
formData.form.controls['email'].setErrors({'incorrect': true});
to manually set the error on that specific field (source).
Upvotes: 2
Reputation: 57919
Update
my bad! you can pass an array, an array is an inmutable value. so if you has
export function findArray(array){
return (control=>{
return array.indexOf(control.value)<0?{error:'not match'}:null
})
}
array=['one','two']
control=new FormControl(null,findArray(this.array))
you can see a simple stackblitz
Really a validator can not has "dynamic" argument. so some like
foolValidator(name:string)
{
return (control:AbstractControl)=>{
return control.value!=name?{error:'it's not the name'}:null
}
}
name="joe"
control=new FormControl(null,foolValidator(this.name))
Only take account 'joe', not the name of variable "name"
You can use bind(this)
, bind change the "scope", see e.g. this link
foolValidator() //see that you not pass the argument
{
return (control:AbstractControl)=>{
//see that you use "this.name"
return control.value!=this.name?{error:'it's not the name'}:null
}
}
name="joe"
control=new FormControl(null,foolValidator().bind(this))
Upvotes: 0