Loading Angular Reactive Forms

Mike Taylor
4 min readJul 11, 2019

--

I’m learning reactive forms right now, and it’s a quagmire of building and updating things that seemed more complicated than I’d ever want. I’m building an internal tool for project planning, and have a need for a reactive form that has a formGroup with two formArrays nested inside it, and formGroups for each of those formArrays. I need those formArrays to be able to add and remove items. I also need to be able to load partially completed forms, because I don’t expect these forms to be completed in a single go. This is where my problem came in. Here’s how I solved it.

Step 1: Prep work — Don’t build your reactive forms manually.

Do. Not. Build. Your. Reactive. Forms. Manually. If. They. Are. Complicated.

Take the example below — I need to create a requirement that has validators (As many as your heart desires) — rather than doing that over and over again, write a function that creates your form group. This can then be re-used later. Even for simple forms, this will come in handy to define them programmatically.

createRequirement() {
return this.formBuilder.group({
project: [''],
requirements: this.formBuilder.array([]),
});
}

That was a simple example with no validation and is simple, but it still has trouble when you want to load data into the form. When you try to load data into the form via patchValue() it won’t load all of the requirements array items, it will only load the first. It appears that this is because patchValue will not traverse the structure and do “deep” patches. The simple example above isn’t as complicated as my real world example, but for sake of discussion, let’s use the above example with some basic validators, and a form that has a formArray of these formGroups:

// This is my base form.
createForm() {
return this.formBuilder.group({
project: ['', Validators.required],
requirements: this.formBuilder.array([]),
});
}
// This creates a requirement that I can add to the requirements //formArray
createRequirement() {
return this.formBuilder.group({
summary: ['', [Validators.minLength(this.minSumLen)]],
description: ['', [Validators.minLength(this.minDesLen)]],
});
}

What you’ll notice above is that the createForm() formgroup has a formArray called requirements. This is an array that we’ll push formGroups into when we load data. Doing this programmatically instead of manually will help when you want to add new requirement items to the requirements formArray.

Step 2: Use getters if you want your life to be simpler.

When you’re using reactive forms, you may want it to be dynamic. By dynamic I mean be able to add and remove formArray items at will. If you have a complicated reactive form, start by creating a getter for that will let you reference the nested portion more simply. Here is what that getter looks like for my form above:

get requirements() {
return this.projectForm.get('requirements') as FormArray;
}

That lets me add utility functions to my component simply, like this:

// Remove a requirement from the formArray at the index given
removeRequirement(index) {
this.requirements.removeAt(index);
}
// Add a requirement from the formArray at the index given
addRequirement() {
this.requirements.push(this.createRequirement());
}

You’ll notice that I’m using the getter to reference the formControl for the requirements formArray. You’ll also notice that I’m using the function that I designed to create a formGroup. This means that adding a validated formgroup to a formArray is one line of logic.

Step 3: Create, update, then push.

For a basic flat reactive form, loading data into it from another source is easy. For a flat form, you can use the patchValue or setValue functions. I prefer patchValue for this, because you can pass partial data in an incomplete form and it works as you’d expect without complaint. This looks like:

// This formData can be loaded from a database, read from a file, or
// defined statically - the point is that you have data you want to // load into your form.
const formData = {
project: 'PP',
epicName: 'My Fancy Epic',
objective: 'This is my fancy objective.',
}
// Loading data into a flat form is easy - just use patchValue
this.projectForm.patchValue(formData);

BUT — When you try this for a nested form, like the one defined below, patchValue will only set the first item (if it exists) and update it.

createForm() {
return this.formBuilder.group({
project: ['', Validators.required],
requirements: this.formBuilder.array([]),
});
}
createRequirement() {
return this.formBuilder.group({
summary: ['', [Validators.minLength(this.minSumLen)]],
description: ['', [Validators.minLength(this.minDesLen)]],
});
}

For these to work, you actually have to loop over the incoming data where there is an array, and create formGroups for each array item. Once you have the formgroup, you can use patchValue to populate the data into the formGroup. Finally, you have to push this formGroup to the formArray.

//Single function to load data into our form.
loadData(formData: Project) {

// Patch the flat data in the form.
this.projectForm.patchValue(formData);
// Patch all requirements.
formData.requirements.forEach((requirement, index) => {
// Create a formGroup that we will patch data to.
const reqFormGroup = this.createRequirement();
// Patch our value to the formGroup
reqFormGroup.patchValue(requirement)
// Push our patched formGroup to our formArray
this.requirements.push(reqFormGroup);
});
}

Just explaining what we’re doing step by step:

  1. We take our formData and use it to patch the “flat” portion of our form that it will patch — it won’t update the formArray group items, but it beats patching each formGroup item individually.
  2. Iterate through our array in our input data items that are arrays — for each item in that array we use our programmatically defined formGroup to create an object.
  3. Update that formGroup object with the current requirement data.
  4. Push that updated formGroup object to the formArray object that it belongs to.
  5. Repeat the same process for any other nested portions of your form.

Your update function will be as simple or as complicated as your form — but if you’ve defined it all programmatically, it should keep your update functions simple, and will let you load formData into a more robust form. This way you can take advantage of reactive forms, and still let your users load their partially completed forms, rather than having to start over!

--

--

Responses (1)