Angular - How to create composite controls that work with formGroup/formGroupName and ReactiveForms
This blog post will show you how to create composite controls in AngularX that allow you to reuse them across your application using the formGroupName directive to data-bind them.
We'll start off with a very basic component that uses a reactive form to edit a person and their address.
First, change the formBuilder code in the parent component so that address is a nested group.
Then we need to change the html template in the parent component so that it uses a new composite control instead of embedding the html input controls directly. To pass the context to our composite child control we need to use the formGroupName attribute.
The html of the edit-address component is quite simple. As with the main component we will need a [formGroup] attribute to give our inner inputs a context to data bind to.
Finally we need our edit-address component to provide the addressFormGroup property the view requires for its [formGroup] directive. To do this we need a ControlContainer reference, which is passed in via Angular's dependency injection feature. After that we can use it's control property to get the FormGroup specified by the parent view. This must be done in the ngOnInit method, as ControlContainer.control is null at the point the embedded component's constructor is executed.
We'll start off with a very basic component that uses a reactive form to edit a person and their address.
Editing a person's name and address
import {Component, OnInit} from '@angular/core'; import {FormBuilder, FormGroup} from '@angular/forms'; @Component({ selector: 'app-root', template: './app.component.html', }) export class AppComponent implements OnInit { public form: FormGroup; constructor(private formBuilder: FormBuilder) {} ngOnInit(): void { this.form = this.formBuilder.group({ name: 'Person\'s name', address_line1: 'Address line 1', address_line2: 'Address line 2', }); } }
<form novalidate [formGroup]="form"> <div> Name <input formControlName="name"/> </div> <div> Address line 1 <input formControlName="address_line1"/> </div> <div> Address line 2 <input formControlName="address_line2"/> </div> </form>
Making the address editor a reusable component
At some point it becomes obvious that Address is a pattern that will pop up quite often throughout our application, so we create a directive that will render the inputs wherever we need them.First, change the formBuilder code in the parent component so that address is a nested group.
ngOnInit(): void { this.form = this.formBuilder.group({ name: 'Person\'s name', address: this.formBuilder.group({ line1: 'Address line 1', line2: 'Address line 2', }) }); }
Then we need to change the html template in the parent component so that it uses a new composite control instead of embedding the html input controls directly. To pass the context to our composite child control we need to use the formGroupName attribute.
<form novalidate [formGroup]="form"> <div> Name <input formControlName="name"/> </div> <address-editor formGroupName="address"></address-editor> </form>
The html of the edit-address component is quite simple. As with the main component we will need a [formGroup] attribute to give our inner inputs a context to data bind to.
<div [formGroup]="addressFormGroup"> <div> Address line 1 <input formControlName="line1"/> </div> <div> Address line 2 <input formControlName="line2"/> </div> </div>
Finally we need our edit-address component to provide the addressFormGroup property the view requires for its [formGroup] directive. To do this we need a ControlContainer reference, which is passed in via Angular's dependency injection feature. After that we can use it's control property to get the FormGroup specified by the parent view. This must be done in the ngOnInit method, as ControlContainer.control is null at the point the embedded component's constructor is executed.
import {Component, OnInit} from '@angular/core'; import {ControlContainer, FormGroup} from '@angular/forms'; @Component({ // Ensure we either have a [formGroup] or [formGroupName] with our address-editor tag selector: '[formGroup] address-editor,[formGroupName] address-editor', templateUrl: './address-editor.component.html' }) export class AddressEditorComponent implements OnInit { public addressFormGroup: FormGroup; // Let Angular inject the control container constructor(private controlContainer: ControlContainer) { } ngOnInit() { // Set our addressFormGroup property to the parent control // (i.e. FormGroup) that was passed to us, so that our // view can data bind to it this.addressFormGroup =this.controlContainer.control; } }
Comments