Reputation: 475
I am trying to figure out the best way to implement cross field validation in Angular.
For example, I have a select field that makes another field mandatory.
I want to be able to:
So far, I came up with three solutions but they don't feel so convincing to me.
Here is a Stackblitz implementation that demos my investigations.
Upvotes: 4
Views: 14348
Reputation: 58099
UPDATE - ANOTHER APPROACH See this SO
UPDATE - A BETTER APPROACH
Create the customValidator
over the form and use the validator to use setError
to the control required. Using setError
, make that Angular adds ng-invalid
for us, ad we needn't subscribe to value change. See:
form: FormGroup = new FormGroup(
{
input1: new FormControl('optional'),
input2: new FormControl(null),
},
{ validators: this.customValidatorForm() },
);
customValidatorForm() {
return (form: FormGroup) => {
const error =
form.get('input1').value != 'optional' && !form.get('input2').value
? { required: true }
: null;
form.get('input2').setErrors(error); //<--see the setErrors
return error;
};
}
See stackblitz
OLD ANSWER
Just use a customValidator
like:
form: FormGroup = new FormGroup({
input1: new FormControl('optional'),
input2: new FormControl(null, this.customValidator()),
});
customValidator() {
return (control: any) => {
if (!control.parent) return null;
let mandatory = control.parent.get('input1').value;
return mandatory != 'optional' && !control.value ? { required: true } : null;
};
}
Another option for not ask for control.parent
it's use .bind(this)
. This allow us have inside the validator to all the variables of our component, and of course access to this.form
:
form: FormGroup = new FormGroup({
input1: new FormControl('optional'),
input2: new FormControl(null, this.customValidator().bind(this)), //<--bind(this)
});
customValidatorBind() {
return (control: any) => {
if (!this.form) return null;
let mandatory = this.form.get('input1').value;
return mandatory != 'optional' && !control.value ? { required: true } : null;
};
}
Well, as we want that when change input1
input2
was checked, you need use, after create the form subscribe to valueChanges
:
this.form.get('input1').valueChanges.subscribe(() => {
this.form.get('input2').updateValueAndValidity();
});
Upvotes: 20
Reputation: 21
Based on the "better approach" on this comment: https://stackoverflow.com/a/57123631/8126632
I would edit it this way to prevent any other validation to be overwritten:
input2: new FormControl('', [Validators.minLength(4)]),
form.get('input2').setErrors({...error, ...(form.get('input2').errors)});
Otherwise a perfect answer.
Upvotes: 2
Reputation: 587
For cross field validation, you can use required
validation of @rxweb/reactive-form-validation
.
You just have to mention conditionalExpression
in your formControl like this:
input2:['', RxwebValidators.required({conditionalExpression:'x => x.input1 == "mandatory"' })]
and set the error message in your app.component.ts like this
ngOnInit(){
ReactiveFormConfig.set({"validationMessage":{"required":"This field is required"}});
}
Here is your complete component code:
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder } from "@angular/forms"
import { RxwebValidators } from '@rxweb/reactive-form-validators';
@Component({
selector: 'app-required-conditionalExpression-validator',
templateUrl: './required-conditional-expression.component.html'
})
export class RequiredConditionalExpressionValidatorComponent implements OnInit {
userFormGroup: FormGroup
constructor(
private formBuilder: FormBuilder )
{ }
ngOnInit() {
this.userFormGroup = this.formBuilder.group({
input1:[''],
input2:['', RxwebValidators.required({conditionalExpression:'x => x.input1 == "mandatory"' })],
});
}
}
Here is your Complete HTML Code:
<div>
<form [formGroup]="userFormGroup">
<div>
<label>Mandatory/Optional </label>
<select formControlName="input1">
<option value="optional">Optional</option>
<option value="mandatory">Mandatory</option>
</select>
<br/><br/>
</div>
<div>
<label>Input2</label>
<input type="text" formControlName="input2"/><br/>
<span>
{{userFormGroup.controls.input2.errors?.required?.message}}
</span>
</div>
</form>
</div>
Here is the Working Example
Upvotes: 3