Photo by Karen Ciocca on Unsplash

Angular Nested Forms and Validation

Summary: Any ngModel in child components inside a parent component with a form element will not be added to that form’s controls array. This post shows one solution to this problem.

At work, we recently came up against an interesting problem using child components in parent form components. I imagine many more teams will encounter this issue, so I’m writing this article to share the solution we found.

Background

Our project is a typical business application comprised mostly of forms. Validation is handled primarily by the server and invalid submissions are returned as HTTP Bad Requests with a model state object describing the errors on each form element. That object looks something like this:

modelState: {
Name: ['Name is required'],
Amount: ['Amount can not exceed $250']
}

Every key on the modelState object matches the name attribute of a form control element so that when the 400 response comes back we can attach the messages to the corresponding FormControl on the NgForm object which trips the built in Angular form validation ng-invalid class and displays the error.

That code looks something like this:

addFormErrors(modelState: any, form: NgForm) {
for key in modelState {
const control = form.controls[key];
if (control == null) continue;
    control.errors.add(modelState[key]);
}
}

The Problem

This was all working like a charm until we discovered that ngModel directives in child components will not get added to the form in any parent component. The following Plunkr shows this problem:

Our first thought on a solution was to pass the parent form into all its child components and manually add the child’s ngModels on ngOnInit. However, this is definitely not ideal. We already had a wrapper component that displayed validation messages automatically on form elements, and we didn't want to add extra steps in order to get this to work.

The Solution

After some extensive Googling, I stumbled across this StackOverflow post with an answer from user Artem Andreev. He suggests the following directive to add to all child components to gain access to their parent’s form:

@Directive({
selector: '[provide-parent-form]',
providers: [
{
provide: ControlContainer,
useFactory: function (form: NgForm) {
return form;
},
deps: [NgForm]
}
]
})
export class ProvideParentForm {}

This works by grabbing the parent’s NgForm and emulating it in the child component. As long as this directive is before any ngModels you want bound to the form, everything will work as expected! This was exactly the solution we needed because it has such a low overhead and allows us to keep the validation architecture we had in place. Here is a final Plunkr demonstrating the solution:

That’s All

Thanks for reading! If you have any other solutions/thoughts on this problem, feel free to leave a comment.