Nested Forms in Angular - Part 2

Published on

After publishing my original Nested Forms in Angular article, my friend and colleague Val Neekman suggested I add a fourth approach, where static factory methods on each of the child components are used instead of a separate factory service. So it is with my thanks to him for the idea, along with some sample code to look at and a much appreciated review of my implementation of the idea, that I am able to write up Part 2 in my Nested Forms in Angular series.

As before, the full source code is available at https://git.thecorams.net/kevin/nested-forms.

[Update January 14, 2020: The code in the repository at the time this article was written has been tagged at tag/part_2]

Parent Component Creates Form; Child Components Define Structure

This approach is a hybrid between the Parent Form and the Global Form approaches from Part 1. As with the Global Form approach, the parent component is responsible for creating the full Reactive Form. The difference is that it does so by composing the full form using methods provided by the child components rather than by a factory service.


export class AppComponent implements OnInit, OnDestroy {
  contact: Contact;
  form: FormGroup;

  private subscription: Subscription;

  constructor(private service: ContactService, private fb: FormBuilder) {}

  public ngOnInit() {
    this.subscription = this.service
      .loadContact()
      .subscribe((data: Contact) => {
        this.contact = data;
        this.form = this.fb.group({
          name: NameComponent.buildForm(data.name),
          addresses: AddressListComponent.buildForm(data.addresses),
        });
      });
  }
}

The HTML templating will be identical to the Global Form approach.

Static Form Builder Methods

Rather than having a separate factory service, this approach uses static methods on each of the child sub-classes. This approach intentionally couples the logic for creating a sub-form structure with the component that would display it, keeping the logic in one place rather than separating it between components and an otherwise unrelated service. The operating principal in this approach is that the component which needs to display the (sub)form to a user will best know what the structure of that form needs to be.

For consistency, a pattern of always naming the static method responsible for creating the form control(s) for a child component buildForm() is used.

Name Component


  static buildForm(name: Name): FormGroup {
    return new FormGroup({
      firstName: new FormControl(name ? name.firstName : ''),
      lastName: new FormControl(name ? name.lastName : ''),
      middleName: new FormControl(name ? name.middleName : ''),
      prefix: new FormControl(name ? name.prefix : ''),
      suffix: new FormControl(name ? name.suffix : ''),
    });
  }

Address List Component


  static buildForm(addresses: Address[]): FormArray {
    const list: FormArray = new FormArray([]);

    if (addresses) {
      addresses.forEach(addr => {
        list.push(AddressComponent.buildForm(addr));
      });
    }

    return list;
  }

Address Component


  static buildForm(addr: Address): FormGroup {
    return new FormGroup({
      line1: new FormControl(addr ? addr.line1 : ''),
      line2: new FormControl(addr ? addr.line2 : ''),
      city: new FormControl(addr ? addr.city : ''),
      state: new FormControl(addr ? addr.state : ''),
      postalCode: new FormControl(addr ? addr.postalCode : ''),
    });
  }

Pros

  • The child components encapsulate the form controls and their display, while keeping the form creation logic separate from the actual template rendering
  • The child components can easily be re-used

Cons

  • The overall shape of the form from the parent component's perspective is not always clear

Conclusion

This approach has a Model, View, Controller separation that is nearly as clean as the Global Form approach has, and arguably has a cleaner encapsulation pattern. The only potential draw-back is that the stronger encapsulation may make it harder for the developer to understand the overall structure of the form data.


This article is licensed under a Creative Commons Attribution 4.0 (CC BY 4.0) License.

The code samples, like the linked Git repository's code they came from, are licensed under the MIT License.