How to use formControlName and deal with nested formGroup?
Asked Answered
S

5

99

As Angular documentation says we can use formControlName in our forms:

<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm" novalidate>
    <div class="form-group">
        <label class="center-block">Name:
            <input class="form-control" formControlName="name">
        </label>
    </div>
</form>

As they say...

Without a parent FormGroup, [formControl]="name" worked earlier because that directive can stand alone, that is, it works without being in a FormGroup. With a parent FormGroup, the name input needs the syntax formControlName=name in order to be associated with the correct FormControl in the class. This syntax tells Angular to look for the parent FormGroup, in this case heroForm, and then inside that group to look for a FormControl called name.

Anyway some months ago I asked this to figure out what is the difference between formControlName and [formControl].

Now my question is: what about use formControlName with nested FormGroups?

For example, if I have the following form structure:

this.myForm = fb.group({
    'fullname': ['', Validators.required],
    'gender': [],
    'address': fb.group({
        'street': [''],
        'houseNumber': [''],
        'postalCode': ['']
    })
});

What is the right way to bind "street" (or "houseNumber" or "postalCode") to related HTML elements using formControlName?

Stubbed answered 12/6, 2017 at 12:24 Comment(2)
Use formGroupName #44432113Heckman
the comment by @Heckman is the solution, simple use formGroupName in your nested html templates before accessing the formControlName as a child html elementStinko
C
154

you can use Form group which is basically a collection of controls ( controls mean the fields given in your html form) define in your typescript syntax and binded to your HTML elements using the formControlName directive ,for example

    this.myForm = fb.group({
        'fullname': ['', Validators.required],
        'gender': [],
        'address': fb.group({
            'street': [''],
            'houseNumber': [''],
            'postalCode': ['']
        })
    });

Template:

    <form [formGroup]="myForm" >
       <div class="form-group">
          <label for="fullname">Username</label>
          <input type="text" id="username" formControlName="fullname" class="form-control">            
       </div>
       <div class="radio" *ngFor="let gender of genders">
          <label>
          <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label>
       </div>
       <div formGroupName="address">
          <div class="form-group">
             <label for="street">Username</label>
             <input type="text" id="username" value="street" formControlName="street" class="form-control">            
          </div>
          <div class="form-group">
             <label for="houseNumber">Username</label>
             <input type="text" id="username" value="street" formControlName="houseNumber" class="form-control">            
          </div>
          <div class="form-group">
             <label for="postalCode">Username</label>
             <input type="text" id="username" value="street" formControlName="postalCode" class="form-control">            
          </div>
       </div>
    </form>

A formGroup can consist of a nested formGroup and hierarchy can continue on ,but in accessing the value the its fairly simple .

Crandell answered 25/9, 2017 at 15:1 Comment(3)
ng-container is a thing to keep in mind if you don't want physically put div in your template, for example, to align elements with flex. Perhaps it would be good to include it in the answer.Aranyaka
Good!, but how validate it?Stelu
@Aranyaka On a similar theme if using css-grid (and that's a great way to lay out a table) you can create a hierarchy in your html (eg. with div) but with display: contents on each wrapper. This will hoist all the controls up as if the div element wasn't there - then the grid will take over and put them in the right place. It's the same exact concept as ng-container but leaves a node in the DOM - which can be useful for adding aria attributes and useful during debugging. developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/…Bringingup
S
25

It is true. Look at formGroupName

this.myForm = fb.group({
    'fullname': ['', Validators.required],
    'gender': [],
    'address': fb.group({
        'street': [''],
        'houseNumber': [''],
        'postalCode': ['']
    })
});

<form [formGroup]="myForm" >
   <div class="form-group">
      <label for="fullname">Username</label>
      <input type="text" id="username" formControlName="fullname" class="form-control">            
   </div>
   <div class="radio" *ngFor="let gender of genders">
      <label>
      <input type="radio" formControlName="gender" [value]="gender">{{ gender }} </label>
   </div>
   <div formGroupName="address">
      <div class="form-group">
         <label for="street">Username</label>
         <input type="text" id="username" value="street" formControlName="street" class="form-control">            
      </div>
      <div class="form-group">
         <label for="houseNumber">Username</label>
         <input type="text" id="username" value="street" formControlName="houseNumber" class="form-control">            
      </div>
      <div class="form-group">
         <label for="postalCode">Username</label>
         <input type="text" id="username" value="street" formControlName="postalCode" class="form-control">            
      </div>
   </div>
</form>
Squiggle answered 20/3, 2018 at 9:15 Comment(1)
That worked for me with ng6 and <ng-container formGroupName="quintessence">.Daubery
S
16

tl;dr:

You can break your form up into components that use your nested formgroups, and formcontrolname can be used as normal.

The approach I tend to use, since usually nested Formgroups are used to designate separate parts of a form, is to break it up into components and pass those components the nested formgroup as an input parameter. So in your case, I would have an address component that takes a formgroup as a parameter:

<app-address [formGroup]="myForm.get('address')"></app-address

And inside of that component, I would just have an @Input() formGroup that would be used in the html:

<div [formGroup]="formGroup">
....

That way you can reference the control name explicitly as you normally would, since it would be part of this formgroup.

Also, keep in mind the form is passed as reference. your changes would be accounted for in the parent component's myForm element, and if you needed access to parts of the form not in you formgroup (validation, change detection, ect ect) you could always pass down the whole form and just define the formgroup to reference the inner group explicitly:

<div [formGroup]="formGroup.get('adress')">

(assuming you pass down the entire form object that is

Happy coding!

Serpens answered 7/4, 2020 at 14:27 Comment(2)
Thank goodness for this... I spent a lot of time making form components that have a FormGroup Input prop. This worked thanks.Citral
Since new TypeScript version this throws an error Type 'AbstractControl | null' is not assignable to type 'FormControl'.. You need to force it to work, like so <div [formGroup]="$any(formGroup.get('adress'))">Hauser
A
0

I am struggling with a problem in Angular 10. I have a "parent" form group "forma" who has a few dependent groups: "company", and at the same time, "company" has two "children" with another groups, msgAccounts and socialMedia. When I fill the form and I submit it, in the backend everything is correct, I can see how these data is stored in the db correctly, but when I receive this json I cannot display the data inside "company.msgAccounts" and "company.socialMedia" in the controls (inputs). This is what I get from the server:

            {
            name:'',
            active: '',
            ........
            company:{
              msgAccounts:{line: "@linedemo", whatsapp: "0325554244"},
              socialMedia: {fb: '', tw: '', is: ''}
             }   
            ..........
            }
    
 this.forma = this.fb.group( {
          siteName  : [ '', [Validators.required, Validators.minLength(5)]],
          siteEmail  : [ '', [Validators.required, Validators.minLength(5)]],
          // defaultLocation: [ '', [Validators.required, Validators.minLength(10)]],
          active  : [ true, [Validators.required, Validators.minLength(5)]],
          mainLanguage: ['' , [Validators.required, Validators.minLength(2)]],
        
          company: this.fb.group({
            name: [''],
            address: [''],
            phone: [''],
            msgAccounts: this.fb.group({
              line: [],
              whatsapp: []
            }),
            socialMedia: this.fb.group({
              fb: [],
              is: [],
              tw: []
            })
          })
        });
    
    And the html: (Sorry about the indentation, when it was not easy using this editor, and I just pasted a part of the code in order to do it as shorter as possible).
    
            <mat-tab-group>   
                    <mat-tab label="settings">
                        <form autocomplete="off" >
                            <ng-template ng-template matTabContent> 
                                <mat-tab-group  [formGroup]="forma">
                                        <mat-tab label="global"> 
                                           // All this fields have are fine
                                         </mat-tab>
    
                                 <mat-tab formGroupName="company" label="company"> 
                                    <div class="card">
                                        <div class="card-header" id="headingTwo">
                                            <h5 class="mb-0">Details</h5>
                                        </div>
                                        <div id="company-details">
                                                <div class="form-group row">
                                                    <div class="col-sm-3">
                                                        <input 
                                                        type="text" 
                                                        class="form-control" 
                                                        id="name" 
                                                        name="name"
                                                        placeholder="Company name"
                                                        formControlName=name>
                                                    </div>
                        
                                                </div>
    
                             <div class="form-group row" formGroupName="msgAccounts">
                                                    <div class="col-sm-6">
                                             
                                                        <input 
                                                        type="text" 
                                                        class="form-control" 
                                                        id="line" 
                                                        name="line"
                                                        placeholder="line"
                                                        formControlName=line>
                                                    </div>
                                                    <div class="col-sm-6">
                                                    
                                                        <input 
                                                        type="text" 
                                                        class="form-control" 
                                                        id="whatsapp" 
                                                        name="whatsapp"
                                                        placeholder="whatsapp"
                                                        formControlName=whatsapp>
                                                    </div>
                                                </div>
    
                                                    
    
                             <div class="form-group row" formGroupName="socialMedia" >
                         
                                                    <div class="col-sm-6">
                                                        <input 
                                                        type="text" 
                                                        class="form-control" 
                                                        id="is" 
                                                        name="is"
                                                        placeholder="Is"
                                                        formControlName=is>
                                                    </div>
                                                </div>                             
                                            </div>
                                       </div>
                                </mat-tab>                 
               </mat-tab-group>  
                            <div class="form-group row">
                            <div class="col-sm-10">
                               <button type="submit" 
                                   (click)="saveConfig()">Save</button>
                            </div>
            </div>
                        
           </ng-template>
          </form>
       </mat-tab>
    </mat-tab-group>  
Accidie answered 13/1, 2021 at 7:35 Comment(0)
F
0

While searching for something similar but in case of a need to build FormGroup dynamically, you can do the following:

Typescript

// ... 
export class MyComponent implements OnInit { 
    public myForm: FormGroup = new FormGroup({
        fullName: new FormControl('', Validators.required),
        gender: new FormControl(),
    });
    
    public constructor(
        private fb: FormBuilder,
    ) {
        // ...  
    }
    
    public ngOnInit(): void {   
        const addressGrp = this.fb.group({}); // OR new FormGroup({})
        this.myForm.addControl('address', addressGrp); // adding address to group ahead
        
        const fieldsToAppend = ['street', 'houseNumber', 'postalCode']; // this can come from API response too
                
        fieldsToAppend.forEach(propName =>
            addressGrp.addControl(propName, this.fb.control(''))); // it can be also:
                                                                   // 1. new FormControl()
                                                                   // 2. this.fb.control('', Validators.required))) 
                                                                   // 3. new FormControl('', Validators.required)

        // this.myForm.get('address').value  === {street: '', houseNumber: '', postalCode: ''}
    }
}

HTML

<form [formGroup]="myForm" >
    ... 
    <div formGroupName="address">
        ...
        <input class="form-control" formControlName="street" />
        ...
        <input class="form-control" formControlName="houseNumber" />
        ...
        <input class="form-control" formControlName="postalCode" />
    </div>
</div>
Ferroelectric answered 29/8, 2023 at 17:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.