I have validation on both frontend and backend sides. Everything is ok with a frontend one, but I can not get to work the backend validation on frontend (manually mark controls as invalid based on backend validation).
The problem is in populating form controls with errors, looks like I cannot just make this.formBuiltWithFormBuilder.setErrors() and pass an object with errors to automatically populate all controls included in a form - as only form became invalid when all field statuses stay the same.
So currently I'm trying to do something like:
// Example of incoming formErrors:
// (it can also be nested for nested forms)
// {
// phone: {
// phoneNumberNoMatch: "The input does not match..."
// }
// }
@Input() public formErrors: ValidationErrors;
public detailsForm: FormGroup;
public ngOnInit() {
this.createForm();
}
public ngOnChanges() {
this.updateFormErrors(this.formErrors);
}
private createForm(account: AccountInterface) {
this.detailsForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required]
});
}
private updateFormErrors(errors: any) {
if (this.detailsForm) {
// Trying to populate included form controls with errors
this.detailsForm.setErrors(errors);
}
}
```
I also thought about walking through the errors object and applying errors manually for each control, but I think this solution is not an ideal as there may be nested forms.
So is it a way to automatically populate form controls with errors?
Looks like there's no built-in solution, so I've created a form helper class to populate errors. In theory it should also populate nested/array forms too, but I've tested it for only plain forms. Let me know if it doesn't ;)
It uses lodash/map method to walk through the errors object.
import { FormGroup, FormControl, FormArray } from '@angular/forms';
import { map } from 'lodash';
export class FormHelper {
public static POPULATE_FORM_GROUP_ERRORS(
rootFormGroup: FormGroup,
errors: any
) {
map(rootFormGroup.controls, (control, name) => {
if (control instanceof FormControl) {
if (errors.hasOwnProperty(name)) {
control.setErrors(errors[name]);
}
}
if (control instanceof FormGroup) {
if (errors.hasOwnProperty(name)) {
FormHelper.POPULATE_FORM_GROUP_ERRORS(control, errors[name]);
}
}
if (control instanceof FormArray) {
if (errors.hasOwnProperty(name) && errors[name] instanceof Array) {
this.iterateOverFormArrayControl(control, errors[name]);
}
}
});
}
private static iterateOverFormArrayControl(
formArray: FormArray,
errors: any[]
) {
errors.forEach((error: any, index: number) => {
const arrayControl = formArray.at(index);
if (arrayControl && arrayControl instanceof FormControl) {
arrayControl.setErrors(error);
}
if (arrayControl && arrayControl instanceof FormGroup) {
FormHelper.POPULATE_FORM_GROUP_ERRORS(arrayControl, error);
}
if (arrayControl && arrayControl instanceof FormArray && error instanceof Array) {
FormHelper.iterateOverFormArrayControl(arrayControl, error);
}
});
}
}
So now my form looks like:
// ... Somewhere at the top
import { FormHelper } from '@app/commons/FormHelper';
// ...
// Example of incoming formErrors:
// (it can also be nested for nested forms)
// {
// phone: {
// phoneNumberNoMatch: "The input does not match..."
// }
// }
@Input() public formErrors: ValidationErrors;
public detailsForm: FormGroup;
public ngOnInit() {
this.createForm();
}
public ngOnChanges() {
this.updateFormErrors(this.formErrors);
}
private createForm(account: AccountInterface) {
this.detailsForm = this.fb.group({
firstName: ['', Validators.required],
lastName: ['', Validators.required]
});
}
private updateFormErrors(errors: any) {
if (this.detailsForm) {
// Populate included form controls with errors
FormHelper.POPULATE_FORM_GROUP_ERRORS(this.detailsForm, errors);
}
}