Search code examples
angularangular-reactive-formsangular-formbuilder

Angular Reactive Form Array with Radio Buttons


i have problems with reactive forms and an array of radio buttons.

My Form looks like this:

Example
There is a player in a row and i have to choose the status.

component:

 <tr *ngFor="let data of player">
        <th>{{data.firstname}} {{data.lastname}}</th>
        <th *ngFor="let stat of status">
        <input type="radio" id="opt1+{{data.id}}" value="{{stat}}" name="option+{{data.id}}" formArrayName="status???"></th>
      </tr>

The player data comes from an API and status is an array.

ts:

this.myForm = formBuilder.group({
    status: this.formBuilder.array,
    })

My example does not work. I need a json-file as result. (playername + status e.g. present) I can't find a way to implement it. Any hints?


Solution

  • Maybe you can do it without using a form.

    Basically have a html setup like this using ngModel to set the status:

    <table>
      <tr>
        <th>Firstname</th>
        <th>present</th>
        <th>missing</th>
        <th>apologizes</th>
        <th>unexcused</th>
      </tr>
      <tr *ngFor="let data of player">
        <th>{{data.firstname}} {{data.lastname}}</th>
        <th *ngFor="let stat of status">
          <input type="radio" id="opt1+{{data.id}}" [(ngModel)]="data.status" value="{{stat}}" name="option+{{data.id}}">
        </th>
      </tr>
    </table>
    

    And a simple function to get the required data:

      getJsonResult() {
        alert(JSON.stringify(this.player.map(x => {
          return {
            playername: x.firstname + ' ' + x.lastname,
            status: x.status
          }
        })));
      }
    

    Working sample on stackblitz.

    UPDATE

    ReactiveForm way requires a bit more code. First there is a FormGroup and then inside a FormArray with players. formArrayName="players"

    <form (ngSubmit)="onSubmit()" [formGroup]="playersForm">
      <table border="1">
        <tr>
          <th>Firstname</th>
          <th>present</th>
          <th>missing</th>
          <th>apologizes</th>
          <th>unexcused</th>
        </tr>
        <tr formArrayName="players" *ngFor="let data of playersForm.get('players').controls; let i = index">
          <ng-container [formGroupName]="i">
            <th>
              <input type="text" formControlName="name" readonly>
            </th>
            <th *ngFor="let stat of status">
              <input type="radio" formControlName="status" value="{{stat}}">
            </th>
          </ng-container>
        </tr>
      </table>
      <br>
      <button type="submit">Submit</button>
    </form>
    

    Typescript part will construct and fill the array.

    playersForm: FormGroup; constructor(private fb: FormBuilder) { }

      ngOnInit(): void {
        this.playersForm = this.fb.group({
          players: this.fb.array([])
        });
    
        this.player.forEach(p => {
          (this.playersForm.get('players') as FormArray).push(
            this.addPlayerFormGroup(p.firstname + ' ' + p.lastname, '')
          );
        });
      }
    
      private addPlayerFormGroup(name?: string, status?: string): FormGroup {
        return this.fb.group({
          name,
          status
        });
      }
    
      onSubmit() {
        alert(JSON.stringify(this.playersForm.value));
      }
    

    In app.moudule.ts import { ReactiveFormsModule } from '@angular/forms'; instead of FormsModule.

    New working stackblitz.

    UPDATE 2nd

    As @Eliseo suggested you can do it without nesting the FormArray.

      ngOnInit(): void {
        this.playersForm = this.fb.array([]);
    
        this.player.forEach(p => {
          this.playersForm.push(
            this.addPlayerFormGroup(p.firstname + ' ' + p.lastname, '')
          );
        });
      }
    

    html:

    <tr *ngFor="let fg of playersForm.controls; index as i">        
    <td>
      <input type="text" [formControl]="fg.get('name')" readonly>
    </td>
    <td *ngFor="let stat of status">
    
      <input type="radio" id="opt1+{{player[i].id}}" value="{{stat}}" name="option+{{player[i].id}}" 
        [formControl]="fg.get('status')">
    </td>  
    

    Stackblitz

    UPDATE 3rd

    If you have data from an observable consider the following approach. I'm using a SWAPI to get some data. Once you receive data. You can map raw data to the required format and then call a method to populate the FormArray.

      ngOnInit(): void {
        this.playersForm = this.fb.array([]);
        let counter = 0;
        const apiUrl = 'https://swapi.co/api/people';    
        this.http.get(apiUrl).subscribe((peoples: any) => {
          this.player = peoples.results.map(p => {
            return {
              id: ++counter,
              name: p.name,
              staus: null
            }
          })
          this.populateForm();
        });
      }
    
      private populateForm() {
        this.player.forEach(p => {
          this.playersForm.push(
            this.addPlayerFormGroup(p.name, '')
          );
        });
      }
    

    Stackblitz