44

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 button there will be another component like wise.
<form [formGroup]="myForm">
<textbox-component></textbox-component>
<radioButton-component></radioButton-component>
</form>

And I want to use Reactive forms for creating this form as I want my html to be untouched and have my form validations through typescript only.

But I cant find any solution how can we have reactive forms nested with components.

Mahesh Saibalwar
  • 1,367
  • 1
  • 9
  • 13
  • Maybe each component will keep it's own validators. At submit, each component emit the value to the parent component through service or event emitter... Not tested, but this should work – mickdev Mar 01 '17 at 12:35
  • I am not able to add formControlName property(which is required for reactive forms) in the html inside child components, it is throwing error of parent directive fromGroup is not present(as it is present in the parent) – Mahesh Saibalwar Mar 01 '17 at 12:40
  • That's why each component should keep it's own formControlName and validators. The parent component role will be instantiate the form and get all data at submit. The children component will fire valid or error event and inform the parent to allow or not the submission ... Take a look to [Nested Model Driven Form](https://scotch.io/tutorials/how-to-build-nested-model-driven-forms-in-angular-2) – mickdev Mar 01 '17 at 12:48
  • I have been through this post, but it is very static example. I am generating form by rendering components dynamically on the fly. Searched net but not finding any help except this post. I think angular 2 is in very initial stage & proper documentation is also not available. So how to go for help for the new features in angular 2? – Mahesh Saibalwar Mar 02 '17 at 05:32

5 Answers5

80

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

  1. something similar on stackoverflow

  2. angular 2 dynamic forms

Community
  • 1
  • 1
Mahesh Saibalwar
  • 1,367
  • 1
  • 9
  • 13
  • 10
    How are you doing validations at the child components? Can we add validations to the from at child component or define all validations at parent? Can you post your .ts file code – Manu Sharma May 18 '17 at 00:32
  • 26
    This answer looks useful, but it is incomplete. Can you please post your TS code? Namely, how and where you set up the FormGroup object and its controls. – user428517 Jun 29 '17 at 17:46
  • 2
    You can follow this tutorial this awesome https://toddmotto.com/component-architecture-reactive-forms-angular#the-presentational-form – Tabares Aug 29 '17 at 19:10
  • I made a simple example based on this solution: https://stackblitz.com/edit/angular-1drscm (based on the switch example at https://coryrylan.com/blog/angular-custom-form-controls-with-reactive-forms-and-ngmodel). – bernardn Mar 08 '19 at 15:36
  • Just my two cents. I just handled an existing Angular project using this approach where you pass around forms as @Input/@Output and it is really a pain in the ass maintaining it. – Alex Coroza Jul 07 '20 at 02:54
  • we can also pass any AbstractControl (FormControl, FormGroup or FormArray) of the main form (if you have multi level formControl elements) as and @Input to the child component – mramsath Jul 22 '20 at 07:02
10

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.

Example

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) {
    }
}
Stormswept
  • 374
  • 5
  • 15
4

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 } ]**
})
PoorInRichfield
  • 1,436
  • 2
  • 19
  • 29
0

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

Juri Sinitson
  • 1,445
  • 1
  • 14
  • 18
-1

just pass same or sub formGroup in form block with [formGroup] binding.

xsilen T
  • 1,515
  • 14
  • 10