2

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?

HansPeter
  • 239
  • 1
  • 2
  • 11
  • 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 – Eliseo Jun 19 '19 at 21:28
  • I think I need a extra name for each row. Right? But I don't know how to do it. – HansPeter Jun 20 '19 at 08:30
  • Sorry, I don't see that you want to control all the players. – Eliseo Jun 20 '19 at 12:53

1 Answers1

4

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

robert
  • 3,981
  • 4
  • 25
  • 31
  • 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 Jun 29 '19 at 14:27
  • @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. – robert Jun 29 '19 at 17:15