GeorgeK
GeorgeK

Reputation: 379

Angular dynamic form with variable content

I need to present a set of questions fetched from an API, and having trouble displaying the form in the template, and getting the correct values of the user's response. The format of the APIs response is the following:

{
  questions: [
  {
    text: 'Is this a question?',
    id: 'question1',
    answers: [
      { text: 'Yes', id: 'answer1' },
      { text: 'No!', id: 'answer2' },
      { text: 'Don't know?', id: 'answer3' }
     ]
   }
  ]
}

I have created a form which looks like the following:

In the component

constructor(private fb: FormBuilder) {
    this.questionsAnswersForm = this.fb.group({
      answers: this.fb.array([])
    }); 
  }
this.initFormWithQAs()
initFormWithQAs(): void {
  this.questions.map(() =>
    (<FormArray>this.questionsAnswersForm.get('answers')).push(
      this.addQuestionFormGroup()
    )
  );
}
 addQuestionFormGroup(): FormGroup {
    return this.fb.group({
      questionId: [null, Validators.required],
      answerId: [null, Validators.required]
    });
  }

In the template

<div formArrayName="answers" *ngFor="let q of questions">
    <h5>{{ q.text }}</h5>
      <form  [formGroupName]="q.id" class="qa-list">
        <label *ngFor="let answer of q.answers">
          <input
            type="radio"
            [value]="answer.id"
            [formControlName]="answer.id"
          />
         <span>{{ answer.text }}</span>
       </label>
     </form>
  </div>

The questionnaire renders on the screen with the correct text, but the radio buttons are not uniquely selected (working like checkboxes), the values on form submit are always null, and i am also getting the following error:

TypeError: Cannot create property 'validator' on string 'question1'

Thanks for the help in advance!

Upvotes: 0

Views: 200

Answers (1)

Ahmed Kesha
Ahmed Kesha

Reputation: 830

I just built something for you to generate dynamic reactive form based on data from api

this is .ts file file

  questions = [
    {
      text: 'Is this a question?',
      id: 'question1',
      answers: [
        { text: 'Yes', id: 'answer1' },
        { text: 'No!', id: 'answer2' },
        { text: 'Dont know?', id: 'answer3' }
      ]
    }
  ]


  questionsAnswersForm: FormGroup;
  constructor(private fb: FormBuilder) {
    this.questionsAnswersForm = this.fb.group({});
    this.initFormWithQAs();
  }

  initFormWithQAs(): void {
    this.questions.forEach((question) => {
      const questionAnswersGroup = this.fb.group({});
      question.answers.forEach(answer => {
        questionAnswersGroup.addControl(answer.id, new FormControl(false));
      });
      this.questionsAnswersForm.addControl(question.id, questionAnswersGroup);
    });
  }

  display() {
    console.log(this.questionsAnswersForm.value);

  }

// To add required custom validation

initFormWithQAs(): void {
    this.questions.forEach((question) => {
      const questionAnswersGroup = this.fb.group({});
      questionAnswersGroup
        .setValidators([function (control) {
          if (!Object.values(control.value).reduce((a, b) => a || b)) {
            return { required: true };
          }
          return null
        }]);
      question.answers.forEach(answer => {
        questionAnswersGroup.addControl(answer.id, new FormControl(false));
      });
      this.questionsAnswersForm.addControl(question.id, questionAnswersGroup);
    });
  }

and this is the html template

<form [formGroup]="questionsAnswersForm">
    <div [formGroupName]="q.id" *ngFor="let q of questions">
        <h5>{{ q.text }}</h5>
        <label *ngFor="let answer of q.answers">
            <input type="radio" [value]="answer.id" [formControlName]="answer.id" />
            <span>{{ answer.text }}</span>
        </label>
    </div>
    <button (click)="display()">Display values</button>
</form>

Upvotes: 1

Related Questions