Search code examples
angularangular-forms

Tabbing between radio buttons groups breaks with angular forms


I have two radio button groups to which I have added angular form controls. The issue I am running into is when I add the controls the radio button groupings are disappearing. In addition, when I inspect the radio button, its name attribute is also disappearing. The effect is that I can no longer tab properly between the different radio button groups. How can I fix this?

Example: https://stackblitz.com/edit/angular-qdqnmo

TS:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  formData = {
          "questions": [
            {
              "refCode": "Question1",
              "answers": [
                {
                  "refCode": "Question1Answer1",
                  "selected": true
                },
                {
                  "refCode": "Question1Answer2",
                  "selected": false
                }
              ]
            },
            {
              "refCode": "Question2",
              "answers": [
                {
                  "refCode": "Question2Answer1",
                  "selected": false
                },
                {
                  "refCode": "Question2Answer2",
                  "selected": false
                },
                {
                  "refCode": "Question3Answer3",
                  "selected": true
                }
              ]
            }
          ]
        };

  formGroup: FormGroup;

  ngOnInit() {
    const group = {};

    this.formData.questions.forEach(question => {
      group[question.refCode] = new FormControl();
    });

    this.formGroup = new FormGroup(group);
  }
}

HTML:

<h2>Normal Buttons:</h2>
<div *ngFor="let question of formData.questions; index as i;">
  <h3>Group {{i + 1}}:</h3>
  <div>
    <div *ngFor="let answer of question.answers" >
      <input type="radio" id="answer.refCode" [checked]="answer.selected" 
      [name]="question.refCode" [value]="answer.refCode" >
      <label for="answer.refCode">{{answer.refCode}}</label>  
    </div>
  </div>
  --------------------------------------------------------------------
</div>

<h2>Angular Buttons:</h2>
<div *ngFor="let question of formData.questions; index as i;">
  <h3>Group {{i + 1}}:</h3>
  <div [formGroup]="formGroup">
    <div *ngFor="let answer of question.answers" >
      <input type="radio" id="answer.refCode" [checked]="answer.selected" 
      [name]="question.refCode" [value]="answer.refCode" 
      [formControlName]="question.refCode">
      <label for="answer.refCode">{{answer.refCode}}</label>  
    </div>
  </div>
  --------------------------------------------------------------------
</div>

Module:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  imports:      [ BrowserModule, FormsModule, ReactiveFormsModule ],
  declarations: [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Solution

  • Off-topic: Interesting question, hadn't noticed this before!

    Using attribute binding instead of property binding seems to solve that issue! With that you get the actual name attribute in the field, just like "normal" buttons.

    But... you have another upcoming issue, so let's tackle that too, which you would eventually run into anyway.

    But first, use attribute binding instead:

    Use:

    [attr.name]="question.refCode"
    

    instead of:

    [name]="question.refCode"
    

    Then on to your other issue. Sure when using [checked] in your template, it looks checked, but actually the form value is not set until you interact with the radio buttons. So remove [checked] and instead set the value to the form control instead. That means you are looking for the answer with selected property which is true (if exists), but you want to use refCode as the value instead. Then I suggest the following:

    this.formData.questions.forEach(question => {
      const preselected = question.answers.find(x => x.selected === true)
      group[question.refCode] = new FormControl((preselected && preselected.refCode) || null);
    });
    

    Your forked STACKBLITZ