Nested forms made simple

Reusable forms in Angular 15

Implement a clean reusable ControlValueAccessor with typed forms

Daniil Rabizo
4 min readJun 26, 2022
Photo by Héctor J. Rivas on Unsplash

Have you as a developer 👨‍💻 ever got the requirement in which you had to implement a reusable form with Angular that can be used in multiple different places? Here is a guide how to create such a nested form with low cohesion to its internal implementation on an example of the address form.

example of reusing address form control with a company address and its customer addresses

I will demonstrate you an example of how you can implement a nested address form group with the city, street and house number inputs and reuse it multiple times with just one line of code as simple form control:

simple usage of nested address form group in a parent template

Step-by-step guide

You need to create a normal FormGroup in parent component, which has some address FormControls which are then bound to the reusable nested form, so that all properties of nested form are in sync with parent form control.

As next you need a reusable address component that implements ControlValueAccessor and Validator interfaces. I will explain each step in detail and why you need it.

  1. First let’s define interfaces for our model: Company and Address. Company has its address and an array of customer addresses. Our typed form will use these interfaces.
company model and form types
address model and form types

2. Create a normal FormGroup for the Company, which must contain a FormControl for the company’s address and a FormArray for customer addresses. You can have any combination and nesting of reusable address forms in your app, but for this example we use this form to bring it to the point.

parent form with controls for reusable nested address form

3. Then you should create a component for the address form group and implement interface ControlValueAccessor in order to let this component behave like a usual form control, although it contains whole Address value. And implement interface Validator to propagate validation errors and form status to parent form control.

There are some necessary methods of ControlValueAccessor that must be implemented in this component. These methods should never be called manually in code, because they are called automatically by Angular forms API when user interacts with the form. And we should implement them to connect parent form control to the nested form:

  • registerOnChange(fn: any) — registers callback function which automatically sends nested form value to parent form control. It should get called on each nested form change, so that parent control is in sync with nested form.
  • registerTouched(fn: any) registers callback function which marks parent form control as touched. So that properties touched, pristine and dirty of parent form control are synchronized with nested form group.
  • setDisabledState(isDisabled: boolean) — is needed to handle disabling of nested form when methods disable() or enable() are called manually on parent form control. In this case we should disable/enable the nested form depending on isDisabled value.
  • writeValue(value: any) — method which is automatically called when setValue(address) or patchValue(address) are called manually on parent form control. We should define how we want to set the value into nested form. Important is that { emitEvent: false } should be passed, so that valueChanges Observable of nested form does not emit in registerOnChange and does not send value to the parent form, because the value already was set into parent control through setValue() or patchValue(). Otherwise there will be an endless loop of sending form value between nested form and parent form control.

And you should implement interface Validator as well, in order to let validation of nested address form be sent to the parent form control validity and errors:

  • validate(control: AbstractControl) — is called by Angular each time when parent form control is validated. So that if nested form is invalid/valid, the parent control also has the same validity state.

The significant aspect of implementing nested reusable forms is providing NG_VALUE_ACCESSOR in order to let component behave like usual form control and NG_VALIDATORS to register a component as validatable form control so that method validate() is called by Angular Forms API.

forwardRef(() => AddressFormComponent) is necessary to refer AddressFormComponent in formControl even before the AddressFormComponent is initialized.

Here is how nested address form template is implemented, just like a normal form:

address nested form template implementation

And in the parent component you can use a nested form as simple as that, just by passing either [formControl] or by passing formControlName(if you have formGroup directive on parent DOM element):

variations of address form usage in parent component

You can then access nested address form value just by getting parent form control value in parent component:

customerAddress.value will return an Address object, which user entered in the nested form, e.g.:

{ 
"city": "Vienna",
"street": "Stephansplatz",
"house": 1
}

--

--