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.
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 passes the data into the child component -->
<app-some-child [data]="data" ...></app-some-child>
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.
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.
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.