Sandro Roth

Follow-up: Data handling in Angular Reactive Forms

Published on

In December 2020 I wrote an article about developing large reactive forms in Angular. What I did not really cover was data handling: how to initialize the form with some data from an API and how to convert the form values back into an object to send it to the backend. Let's do this now.

Repetition: Setup

Let's briefly take a look at the main setup we had in the previous blog post.

Architecture of a reactive form with child components

  1. The parent components wants to render a large form. This form is splitted up into multiple child components. Each child component is responsible for a self-contained part of the form.

  2. Each child component creates its own form group with all controls for which the component is responsible.

  3. After creating the form group, the child component emits a formReady event, providing the form group as parameter.

  4. The parent component receives these events from all children and adds each received form group to its own main form group.

  5. The parent component can use its form group to get the validation status and disable the submit button until all fields are valid.

Now let's go on and add some data.

Initializing the form

You may have a backend that sends you some data and you want to initialize the form with this data. We could do this directly in the parent component as it owns the main form and this gives it access to all child form groups.

Doing this would lead to a few problems:

  • The parent component needs to know how the form of each child is structured. If you refactor a child component, e.g. rename a field, you also must adjust the parent component. That's not good encapsulation.

  • The object that you receive from the backend may not reflect how your form looks like. The form may have a different structure or you may need to transform some values. Doing all of that in the parent component may lead to a large and hard to maintain file.

So let's move that into each child component instead. The parent component passes the data into each child component and each child can use that data to initialize the form.

<!-- /parent.component.html -->
<!-- Parent passes the data into the child component -->
<app-child-one [data]="data" ...></app-child-one>
// /child-one.component.ts
export class ChildOneComponent implements OnInit {
  @Input()
  data: Data;

  form: FormGroup;

  ngOnInit(): void {
    this.form = this.fb.group({
      firstName: [this.data.firstName, [Validators.required]],
      lastName: [this.data.lastName, [Validators.required]],
    });
  }
}

Note that if you receive your data asynchronous (i.e. from a backend) you should not render your child components until your data is ready. The data will not be ready otherwise in ngOnInit.

Getting the form values

Now the user fills out the form and submits it. How can we read all values from the form? There is a value property on the form group but this reflects the form structure and not your data structure that you need.

Data structure vs. Form value structure

We need to transform the form value into the data structure expected by the backend. It's the same problem as with initializing the form: doing that in the parent component is not maintainable in long term. We should keep that in each child component. Here is how it works:

  1. The user edits a value in a form field.

  2. The child component uses its form value to build a partial data object that only contains the properties that are related to this child form.

  3. The child component emits an event and provides this partial data object to the parent.

  4. The parent merges the received incomplete object into a complete data object.

  5. When submitting the form the parent component can send that data object to the backend.

Merging data from the child in the parent component

The parent component always has an up-to-date data object that reflects the current form value.

Demo

Here is the demo on StackBlitz:

Wrap Up

That it's. We now have a working reactive form with initial data 🎉.

Each child component is responsible for rendering a part of a form, initializing all fields of that part and read out the edited form values. Even if you have a very large and complex form, your parent component is small and easy to maintain.