nested-forms/apps/parent-form/README.md

3.1 KiB

Components Creating Own Form Controls

When a component becomes sufficiently complex, or the developer wishes to be able to reuse parts of it elsewhere, the component can be refactored into sub-components. One approach to binding such sub-components to their containing parent component is to pass the parent component's FormGroup in as an @Input parameter. Additionally, the data that each child sub-component needs is passed into an @Input. The children create their own FormControls as needed, and add them to the parent FormGroup provided to them. This approach greatly simplifies the code and template of the parent component:

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

  private subscription: Subscription

  constructor(private fb: FormBuilder, private service: ContactService) {
    this.form = this.fb.group({});
  }

  public ngOnInit() {
    this.subscription = this.service.loadContact().subscribe((data: Contact) => {
      this.contact = data;
    });
  }
}
<form [formGroup]="form">
  <nested-forms-name
    [name]="contact.name"
    [parent]="form"
  ></nested-forms-name>
  <nested-forms-address-list
    [addresses]="contact.addresses"
    [parent]="form"
  ></nested-forms-address-list>
</form>

The nested-forms-name component is responsible for creating the form controls binding to the Contact's name, and the nested-forms-address-list component is responsible for iterating over the Contact's addresses and binding to them using the nested-forms-address (singular) component. For example, the nested-forms-name would be implented as so:

@Component({
  selector: 'nested-forms-name',
  templateUrl: './name.component.html',
  styleUrls: ['./name.component.css']
})
export class NameComponent implements OnInit {

  @Input() name: Name;
  @Input() parent: FormGroup;

  group: FormGroup;

  constructor(private fb: FormBuilder) {
  }

  ngOnInit() {
    this.group = this.fb.group({
      firstName: new FormControl(this.name ? this.name.firstName : ''),
      lastName: new FormControl(this.name ? this.name.lastName : ''),
      middleName: new FormControl(this.name ? this.name.middleName : ''),
      prefix: new FormControl(this.name ? this.name.prefix : ''),
      suffix: new FormControl(this.name ? this.name.suffix : ''),
    });

    if (this.parent) {
      this.parent.addControl('name', this.group);
    }
  }
}

Calling this.parent.addControl(....) is what ensures that the controls created in the child component are made part of the over-all form.

Pros

  • The parent component is easy to understand and maintain
  • Each child component encapsulates its form controls and template
  • The child components can easily be re-used in other "parent" components

Cons

  • The creation of the form controls is tightly coupled with the templates, to the point that if a child component is not rendered for some reason, the form controls won't exist either.
  • Since each child component encapsulates its form controls, the overall shape of the form data is not always clear