DEV Community

Preston Lamb
Preston Lamb

Posted on • Originally published at Medium on

Manage Complex Reactive Forms in Angular

tldr;

We've all had to manage complex forms in our Angular apps, and it can get messy really quickly. Making sure to use all that Angular has to offer for reactive forms helps make the experience better. This can be done by using nested FormGroups and FormArrays. In this article, we'll take a look at how this can help us manage a complex form.

What's the Problem?

I work at a health insurance company for my full time job. As you can imagine, we deal with a lot of forms, and some of them are very complex. For example, one form allows an employer to add an employee and their dependents to an insurance policy. The employee form alone has about 20 fields that need to be filled out, and then each dependent has 8 required fields and an optional set of address fields. There is no limit to the number of dependents that can be added to the form. The logic in the front end began to quickly grow out of control, with us manually checking to see if the form data was valid (instead of using the FormGroup.valid attribute) and making sure that the reusable forms were emitting the data to the parent component to be saved. The complexity caused us to hesitate any time we needed to edit the page. Finally I decided it was time to take another look at the page and simplify it and leverage the power of Angular and what it gives us for free.

The Form Setup

I've been working on a form at work that allows an employer to add an employee and their dependents to their health plan. The form can get complicated, since there can be multiple dependents. We were reusing the (reactive) dependent form for each dependent that was added, but were manually determining if the required fields were filled out. Each time the valuesChanged observable emitted a new value, we'd determine the validity of the form. We were doing something similar with the employee form. In addition, we also were manually taking the value of the form and emitting that to the parent container so it could be submitted when necessary.

All this was really complicated, so I did a little refactoring. The layout was similar: there was a container component for the page, an employee form component, and 0 or more dependent forms. But instead of the container component storing the value of each form after responding to a new Output emission, the children components now emit their form to the container component when the form is initialized and stored in a different form there. I know, this is confusing, but this visualization should help:

// container.component.ts

export class ContainerComponent {
    public form: FormGroup;

    constructor(private _fb: FormBuilder) {}

    ngOnInit() {
        this.form = this._fb.group({
            employeeForm: null,
            dependents: this._fb.array([])
        })
    }

    get dependentSubForms () {
        return this.form.get('dependents') as FormArray;
    }

    employeeFormReady(form: FormGroup) {
        this.form.setControl('employeeForm', form);
    }

    dependentSubFormReady(form: FormGroup) {
        this.dependentSubForms.push(form);
    }
}

Enter fullscreen mode Exit fullscreen mode

In this container component, we create the main form. This is how we'll manage the data from the employee form and all the dependents. When a form is initialized, we'll add it to the main form object. In the case of the employee form, we set the employeeForm control to a FormGroup, which is contained in a child component. The dependent forms are managed essentially the same way, but they are added to a FormArray instead of a single control.

At this point, the form in ContainerComponent will get all updated values from the children components, as well as set its validity based on the forms coming from the children components. When we're ready to get the value from all the forms, the form object in the ContainerComponent will contain all the data entered.

You can play with a demo here on StackBlitz.

Conclusion

This may seem a little complicated, but it is easier than the alternative method that we were managing previously. Angular is really powerful, and there's no need for us to do its job for it. This manner is also flexible. It can be used in the way I did above, but I also used the same format recently to build a multi step form. Each time a new portion of the form was shown on the page, we added a new control to the form. It made managing each step really easy.

This is also not the only way to do this. One way that was suggested was using ControlValueAccessors, and that is another way that I'll look into in the future to see how it compares to what we're now doing

Discussion (0)