Skip to content

Follow-up: Data handling in Angular Reactive Forms

In a previous article we implemented a reactive form in Angular with child components. Now we'll learn how to initialize the form with data fetched by an API and how to convert the form values back to a data object.

Published
Mar 13, 2021
Updated
Oct 23, 2021
Reading time
5 min read

Article is outdated

This article is based on Angular 12 and will not be updated anymore (but will be kept for reference). Read my new article Handling large, typed reactive forms in Angular which is a rewrite of this one, uses the latest Angular version and strictly typed forms.


October 2021: The article has been rewritten and the demo has been updated to Angular 12.


Please read my previous blog post large reactive forms in Angular first if you haven't already. You will learn how to build reactive forms in Angular that are split up into multiple components.

Repetition: Forms with child components

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

Each child component creates a new form group and provides the form group to the parent component by emitting an event.

The parent component creates its own form group and attaches the form group of each child to it. The main form group is then used to check the validation status, resetting the form, ...

Use Case

Let's say you have form. You first want to make a request to a backend to fetch some data and then initialize your form with the received values. The user can edit the form values and submit it. Then you want to read all values from the form and send it to the backend.

You may need to apply some data transformation when initializing the form with the backend data and when sending them back to the backend. For example, you may receive a date in a specific format as a string and you want to convert it to a date object. When submitting the form, you need to transform the date object back into a string.

As our form is composed of several components we want to make sure that these transformation logic is part of each component that it belongs to. The parent should not know anything about that child form related stuff.

Implementation

Initialize the form

The initialization of the form could be done in the parent component. It has access to all form fields because each child provides their form group via event. But doing this in the parent would be wrong. The parent should not know any implementation details of the child components like how the form fields are structured and named.

Instead, we can provide the data to all child components as an input value and each child can use that data to initialize its own form group.

/parent-component.html
<!-- Parent passes the data into the child component -->
<app-some-child [data]="data" ...></app-some-child>
/some-child.component.ts
export class SomeChildComponent 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]],
    });
  }
}

The child component receives the data as input and initializes the form with it in ngOnInit. Each child can also do some data transformation if required.

The example above expects that the data is ready in ngOnInit. If you fetch the data from a backend you should not render the child components until the request completed, the data would not be available in ngOnInit otherwise. Or you can implement the data input as a setter function and update the form values whenever the data input changes.

Read the form values

Now the user fills out the form and submits it. How can we read all values from the form? The parent component could use the value property of the form group which includes all values of all child component form groups.

The problem is that this value reflects the form structure, so it depends on how each child structures their form controls and groups. While it's possible to use the same structure for the form as your actual data (that you fetched from the backend) it's often not very practical. The backend and data structure may not be under your full control and may have an impractical format. Or one day you refactor the form because the UI of the form changed but you don't want to touch the data structure and database.

Data structure vs. Form value structure

What we want to do instead is using the form value and transform it back to the data structure defined by the backend API. And again, we'll do that in each child component because only the child knows how its form is structured.

The big question is, how the parent component can receive the final data entered by the user. We could implement a getData() method on each child component and the parent could call that method for each child when the form is submitted. But we can use event emitters instead.

Each child controls their own form group and can use the form group to get notified whenever the form value changes by subscribing to the valueChange observable. It then needs to transform the value and emit an event with the data. The parent component can subscribe to the events of all child components.

Merging data from the child in the parent component

When the user submits the form, the parent component has always the latest data object and can send it to the backend.

Demo

Here is the demo on StackBlitz:

stackblitz.com/edit/large-reactive-forms-data-handling

Summary

We now have a working reactive form with initial data 🎉.

Each part of the form is rendered by its own component. That component has its own form group and initializes it with the data provided by the parent component. Whenever a value is changed, the child component generates the new data object and emits an event to provide it to the parent.

The parent component always has the latest data object and can send it to the backend when the form is submitted.

That's it, I hope it was helpful. Let me know if you have further questions or feedback about this topic.