My requirement is that I need to create a form with nested components. I am creating components for each form field means for textbox there will be one component, for radio
Additional note to Mhesh's answer, you can build this sames solution without injecting [parentFormGroup]
in the HTML. You can do this by following this Stack Overflow post on reusable form groups.
This is really nice.
To take the existing solution, you can do the same thing, except:
Your parent component can be like this, without any additional parameters passed in
<form [formGroup]="myForm">
<child-textbox-component></child-textbox-component>
<child-radio-button-component></child-radio-button-component>
</form>
Note additionally you can set formgroups like this:
<form [formGroup]="myForm">
<child-textbox-component></child-textbox-component>
<child-radio-button-component formGroupName="myGroup"></child-radio-button-component>
</form>
child-textbox-component
<div class="form-group" [formGroup]="controlContainer.control">
<label>
{{control.caption}}
</label>
<input class="form-control" type="text" [title]="control.toolTip"
[attr.maxlength]="control.width" [name]="control.name"
[value]="control.defaultValue" [formControlName]="control.name"/>
</div>
To enable this you want to inject a ControlContainer
into your @Component
@Component({
moduleId: `MODULE_ID_HERE`,
selector: "child-textbox-component",
templateUrl: "childTextbox.component.html",
})
export class ChildTextboxComponent {
constructor(private controlContainer: ControlContainer, OTHER_PARAMETERS) {
}
}
Same problem when trying to use a nested date picker. The solutions above didn't work for me for me. I solved the problem by emitting the date on change and by acessing the form conrol directly.
calendar-input.component.ts
@Component({
selector: 'app-calendar-input',
templateUrl: '...',
})
export class CalendarInputComponent {
@Output() dateChanged = new EventEmitter<Date|null>();
onClose() {
this.dateChanged.emit(this.currentDate);
}
...
}
The template
<app-calendar-input
...
(dateChanged)="setFormControlValueByItem(item, col.propName, $event)">
</app-calendar-input>
And the component with the form group and the form controls:
setFormControlValueByItem(item: FormItem<I, FormGroup>, propName: string, value: any): void {
item.frameworkFormGroup.controls[propName].setValue(value);
item.frameworkFormGroup.controls[propName].markAsTouched();
item.frameworkFormGroup.controls[propName].markAsDirty();
}
item.frameworkFormGroup
is in my case the instance of FormGroup and propName
is the field name I would normall write to the attribute formControlName
.
A disadvatage of this solution is that it makes the code less resistant against breaking chanches in the FormGroup and the FormConrol API in some cases.
See also: https://angular.io/api/forms/FormGroup and https://angular.io/api/forms/FormControl
To extend the list of possible answers, this article by Alexey Zuev suggests using provide:ControlContainer
and useExisting:NgForm
in the component decorator as a way to pass the ngForm
directive into a child component.
@Component({
selector: 'registrant',
templateUrl: 'app/register/registrant.component.html',
**viewProviders: [ { provide: ControlContainer, useExisting: NgForm } ]**
})
After my research & experiments I found one answer to my question, so answering it myself. If it saves someone's time then I will be happy.
If you want to create reactive forms with nested components then you can do as below
Here I am creating a form with two nested components one for textbox & other for radio button
Your parent component can be like this
<form [formGroup]="myForm">
<child-textbox-component [parentFormGroup]="myForm">
</child-textbox-component>
<child-radio-button-component [parentFormGroup]="myForm">
</child-radio-button-component>
</form>
We are passing FormGroup object as input to child components which has been created in the parent component as input to the child components, they will use this FormGroup object in their component to design specific control of the class
Your child components will be like this
child-textbox-component
<div class="form-group" [formGroup]="parentFormGroup">
<label>
{{control.caption}}
</label>
<input class="form-control" type="text" [title]="control.toolTip"
[attr.maxlength]="control.width" [name]="control.name"
[value]="control.defaultValue" [formControlName]="control.name"/>
</div>
child-radio-button-component
<div class="form-group" [formGroup]="parentFormGroup">
<label>
{{control.caption}}
</label>
<div>
<label *ngFor="let value of control.values; let idx = index"
class="radio-inline" [title]="control.tooltip">
<input type="radio" [name]="control.name" [formControlName]="control.name"/>
{{ value }}
</label>
</div>
</div>
Here control is the model class holding data to be displayed for these child components.
This way you can have your form to be generated using nested components, so that you need not have your form (can say large form) in single component. You can break it down to as many sub components & form will be easy to create & maintain also using reactive forms of angular 2. You can also easily add validations too.
I followed these links before answering this
something similar on stackoverflow
angular 2 dynamic forms
just pass same or sub formGroup in form block with [formGroup] binding.