4

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?

3
  • Hans, you has not a FormArray, your form has only two fields: playerName and status (and status can be one of the value:·present, missing, apologizes and unexcused). You use an array to show the radiobuttons but you only has ONE formName Commented Jun 19, 2019 at 21:28
  • I think I need a extra name for each row. Right? But I don't know how to do it. Commented Jun 20, 2019 at 8:30
  • Sorry, I don't see that you want to control all the players. Commented Jun 20, 2019 at 12:53

1 Answer 1

6

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

Sign up to request clarification or add additional context in comments.

3 Comments

I need more help. Currently the playersForm has only the fb.array. How can I add more fields? e.g. I need a teamname and year data field. Is formBuilder.group an option?
@HansPeter If you need more fields beside players check the first UPDATE there you have this.playersForm = this.fb.group({... as a main parent group inside that formGroup you can add more fields including the form array for the players etc.
OMG THANK YOU @robert for this comprehensive answer. I had to make some minor changes to the code under Update (1st one), here is my stackblistz: stackblitz.com/edit/angular-5hb1gh?file=src/app/… Specifically, I had to change the value of the input element to get the formControlName to stick and allow choosing a single radio per question (player).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.