I have seen a few StackOverflow issues with this question, but none of them seem to have proven answers that work.
I am building a dynamically created Reactive Form Using Angular. StackBlitz here . The user has the option to add "groups" and "conditions" within the groups. Each group can be further nested using FormArrays.
The form works as expected. Here are the main pieces of the form:
This is what a basic object looks like:
{
// appcomponent level
"statement": {
//groupcontrol level
"groups": [
{
// conditionform level
"conjunctor": null,
"conditions": [
{
"variable": null
}
],
"groups": []
}
]
}
}
When I set up validation, I want everything to be required. So I went through the FormBuilder, on the parent and children components, and set Validators.required
to true on all form controls.
this._fb.control({
conjunctor: null,
conditions: [[], Validators.required],
groups: [[], Validators.required]
})
(this is the case on all controls)
The problem is, the form now always is valid. Even when the form is not actually valid. Is it possible to check form validity while using nested child components?
Here is some more information about this specific form, if required:
Cannot Find Control with Path Using ngIf on Recursive Angular Form
Angular Deeply Nested Reactive Form: Cannot find control with path on nested FormArray
Basically you must implement the Validator
(not Validators
- plural) interface. It has one mandatory method:
validate(c: AbstractControl) => ValidationErrors;
The method validate
should return null
if the control is valid and any other object if it is invalid.
One more thing is that you must provide NG_VALIDATORS
(now, using plural) in the same way you provided NG_VALUE_ACCESSOR
.
In summary:
@Component({
...
providers: [
...
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => GroupControlComponent),
multi: true
}
]
})
export class GroupControlComponent implements Validator {
...
// Notice that we don't need to use the c parameter passed to the method.
// We are using the `_form` instance property to verify what we need
validate(c: AbstractControl): ValidationErrors {
return this._form.valid ? null : {errors: 'form is invalid'};
}
...
}
You must do it to your GroupControlComponent
and the ConditionFormComponent
.
[UPDATE]: You also need to take care of the dynamically created controls. Inside group control, let's look at the one single component that has an input: the condition control. Instead of:
_addCondition() {
this._conditionsFormArray.push(
this._fb.control({variable: null})
);
}
You should initialize the control with a validator that checks for the validity of variable
because the underlying control associated with the variable
attribute will only check this out after the user interacts with the input inside the ConditionFormComponent
. So, you should write it like this:
_addCondition() {
this._conditionsFormArray.push(
this._fb.control({variable: null},
(c: AbstractControl) =>
c.value && c.value.variable ? null : {'required': true}
)
);
}
Where this is a validation function that checks for the validity of the variable
attribute:
(c: AbstractControl) =>
c.value && c.value.variable ? null : {'required': true}
If you don't do that, it'll override the validation in the underlying ConditionForm
control (which states that the input is required). And you could have a weird state where the outer form (in GroupControl
) is invalid, but the inner form (in the ConditionForm
) is valid. As you're validating it on the outer form, you basically could even remove the Validators.required
in the inner form and rely just on the outer form validation.