0

I opened question about dynamically generate form controllers,after that I have some issues with dynamically template generate and controllers generate.

in this project,mainly I have 4 types of questions in an array.I have to generate these questions dynamically

question types are,

  1. MCQ ( only have to select one answer)

  2. Multiple Select ( User can select multiple answers and at least one required)

  3. Ranking question ( user have to give the correct order of answers. answers should be unque)

  4. Descriptive ( Users own answer can give)

here is my html code

<div class="container">
    <div class="row">
        <div class="col-md-12">
            <div class="card">
                <div class="card-header  bg-transparent border-success">
                    <h3>15 questions</h3>
                </div>
                <div class="card-body">
                    <div class="row">
                        <div class="col-md-12">
                            <form [formGroup]="surveyQuestionForm">
                                <div *ngFor="let question of questions; let i=index">
                                    <div [ngSwitch]="question.qType">
                                        <div *ngSwitchCase="1">
                                            <div class="form-group">
                                                <label class="control-label"> {{question.qNo}})
                                                    {{question.question}}</label>
                                                <div class="ml-3">
                                                    <table>
                                                        <tr *ngFor="let anwr of question.answers; let a=index">
                                                            <td>{{a+1}}. {{anwr}} </td>
                                                            <td>
                                                                <div class="custom-radio custom-control">
                                                                    <input type="radio" class="custom-control-input"
                                                                        id="q{{question.qNo}}_{{a}}"
                                                                        name="q{{question.qNo}}" value="{{a+1}}"
                                                                        formControlName="q{{question.qNo}}"
                                                                        [ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && formSubmitted}" />
                                                                    <label class="custom-control-label"
                                                                        for="q{{question.qNo}}_{{a}}"></label>
                                                                </div>
                                                            </td>
                                                        </tr>
                                                        <div class="text-danger"
                                                            *ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('required') && formSubmitted">
                                                            Answer required</div>
                                                    </table>

                                                </div>
                                            </div>
                                        </div>
                                        <div *ngSwitchCase="2">
                                            <div class="form-group">
                                                <label class="control-label"> {{question.qNo}})
                                                    {{question.question}}</label>
                                                <div class="ml-3">
                                                    <table>
                                                        <tr *ngFor="let anwr of question.answers; let b=index">
                                                            <td>{{b+1}}. {{anwr}} </td>
                                                            <td>
                                                                <div class="custom-checkbox custom-control">
                                                                    <input type="checkbox" class="custom-control-input"
                                                                        id="q{{question.qNo}}_{{b}}" value="{{b+1}}"
                                                                        formControlName="q{{question.qNo}}"
                                                                        [ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && formSubmitted}" />
                                                                    <label class="custom-control-label"
                                                                        for="q{{question.qNo}}_{{b}}"></label>
                                                                </div>
                                                            </td>
                                                        </tr>
                                                        <div class="text-danger"
                                                            *ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('atLeastOneRequired') && formSubmitted">
                                                            At least One Answer required</div>
                                                    </table>

                                                </div>
                                            </div>
                                        </div>
                                        <div *ngSwitchCase="3">
                                            <div class="form-group">
                                                <label class="control-label"> {{question.qNo}})
                                                    {{question.question}}</label>
                                                <div class="ml-3">
                                                    <table>
                                                        <tr *ngFor="let anwr of question.answers; let a=index">
                                                            <td>{{a+1}}. {{anwr}} </td>
                                                            <div class="invalid-feedback"
                                                                *ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('required')">
                                                                Answer required</div>
                                                            <div class="invalid-feedback"
                                                                *ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('max')">
                                                                max value</div>
                                                            <div class="invalid-feedback"
                                                                *ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('min')">
                                                                min value</div>
                                                            <div class="invalid-feedback"
                                                                *ngIf="surveyQuestionForm.get('q'+ question.qNo).touched && surveyQuestionForm.get('q'+ question.qNo).hasError('notAllUnique')">
                                                                Already inserted value</div>
                                                            <td>
                                                                <input type="number" style="width:40px;"
                                                                    id="q{{question.qNo}}_{{a}}"
                                                                    [ngClass]="{'is-invalid': surveyQuestionForm.get('q'+ question.qNo).errors 
                                                                    && surveyQuestionForm.get('q'+ question.qNo).touched}"
                                                                    formControlName="q{{question.qNo}}"
                                                                    class="text-center" />
                                                            </td>
                                                        </tr>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                        <div *ngSwitchCase="4">
                                            <div class="form-group">
                                                <label class="control-label"> {{question.qNo}})
                                                    {{question.question}}</label>
                                                <div class="ml-3">
                                                    <table>
                                                        <th width="auto"></th>
                                                        <th></th>

                                                        <tr>

                                                            <td><textarea class="form-control" rows="5" id="comment" name="text"
                                                                [ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && 
                                                                surveyQuestionForm.get('q'+ question.qNo).touched}"
                                                                formControlName="q{{question.qNo}}"></textarea></td>
                                                        </tr>
                                                        <div class="text-danger"
                                                            *ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('required') && formSubmitted">
                                                            Answer required</div>
                                                    </table>
                                                </div>
                                            </div>
                                        </div>
                                        <div *ngSwitchCaseDefault></div>
                                    </div>

                                </div>


                            </form>
                        </div>
                    </div>
                    <div class="row">
                        <div class="col-md-12">
                            <button (click)="onSubmit()" class="btn btn-primary">Submit</button>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

Here is my typescript code

surveyQuestionForm: FormGroup;
  formSubmitted = false;

  constructor(private fb: FormBuilder) { }
  questions: any = [
    {
      id: 11,
      surveyNo: 5,
      qNo: 1,
      question: 'What is the country you would like to travel?',
      qType: 1,
      noAnswrs: 4,
      answerType: 1,
      answers: ['America', 'Australia', 'India', 'England']
    },
    {
      id: 12,
      surveyNo: 5,
      qNo: 2,
      question: 'What type of credit cards do you have?',
      qType: 2,
      noAnswrs: 4,
      answerType: 1,
      answers: ['Visa', 'Mastercard', 'American Express', 'Discover']
    },
    {
      id: 13,
      surveyNo: 5,
      qNo: 3,
      question: 'Please rank the following features in order of importance,where 1 is the most important to you.?',
      qType: 3,
      noAnswrs: 4,
      answerType: 1,
      answers: ['Location', 'Confort', 'Service', 'Value for money']
    },
    {
      id: 14,
      surveyNo: 5,
      qNo: 4,
      question: 'What is your idea about our institute?',
      qType: 4,
      noAnswrs: 0,
      answerType: 1,
      answers: []
    }
  ];
  ngOnInit() {
    this.createForms();
  }
  createForms(): any {
    this.surveyQuestionForm = this.fb.group(
      this.questions.reduce((group: any, question: { qNo: string; }) => {
        return Object.assign(group, { ['q' + question.qNo]: this.buildSubGroup(question) });
      }, {})
    );
  }

  private buildSubGroup(question) {
    switch (question.qType) {
      case 1:
        return [Validators.required];
      case 2:
        return this.fb.group(
          question.answers.reduce((subGroup, answer) => {
            return Object.assign(subGroup, { [answer]: [false] });
          }, {}), { validators: [this.atLeastOneRequired()] }
        );
      case 3:
        return this.fb.group(
          question.answers.reduce((subGroup, answer) => {
            return Object.assign(subGroup, { [answer]: ['', [Validators.required, Validators.min(1), Validators.max(3)]] });
          }, {}), { validators: [this.uniqueNumbersValidator()] }
        );
      case 4:
        return this.fb.group({ answer: ['', [Validators.required]] });
      default:
        throw new Error('unhandled question type');
    }
  }



  atLeastOneRequired() {
    return (ctrl: AbstractControl) => {
      const fg = ctrl as FormGroup;
      const atLeastOneTrue = Object.values(fg.controls).some(fc => !!fc.value);
      return (atLeastOneTrue) ? null : { atLeastOneRequired: true };
    };
  }

  uniqueNumbersValidator() {
    return (ctrl: AbstractControl) => {
      const fg = ctrl as FormGroup;
      let allUnique = true;
      const values = [];
      Object.values(fg.controls).forEach(fc => {
        const val = fc.value;
        if (val && allUnique) {
          if (values.includes(val) && allUnique) {
            allUnique = false;
          }
          values.push(val);
        }
      });
      return (allUnique) ? null : { notAllUnique: true };
    };
  }

  onSubmit() {
    this.formSubmitted = true;
    console.log(this.formSubmitted);
  }

I can see this error,

control.registerOnChange is not a function

here is the stackblitz link https://stackblitz.com/edit/angular-nya7l9

Please help me to solve this issue.

thanks

2 Answers 2

1

first, issue with your radio building and binding:

built it like this:

  case 1:
  case 4:
    return this.fb.group({ answer: ['', [Validators.required]] });

and bind like this:

  <div class="form-group" formGroupName="{{'q' + question.qNo}}">
      <label class="control-label"> {{question.qNo}})
          {{question.question}}</label>
      <div class="ml-3">
          <table>
              <tr *ngFor="let anwr of question.answers; let a=index">
                  <td>{{a+1}}. {{anwr}} </td>
                  <td>
                      <div class="custom-radio custom-control">
                          <input type="radio" class="custom-control-input"
                              id="q{{question.qNo}}_{{a}}"
                              name="answer" value="{{a+1}}"
                              formControlName="answer"
                              [ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && formSubmitted}" />
                          <label class="custom-control-label"
                              for="q{{question.qNo}}_{{a}}"></label>
                      </div>
                  </td>
              </tr>
              <div class="text-danger"
                  *ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('required') && formSubmitted">
                  Answer required</div>
          </table>

      </div>
  </div>

use the formGroupName directive up top then just access the answer control statically.

next your checkbox binding:

<div class="form-group" formGroupName="{{'q' + question.qNo}}">
    <label class="control-label"> {{question.qNo}})
        {{question.question}}</label>
    <div class="ml-3">
        <table>
            <tr *ngFor="let anwr of question.answers; let b=index">
                <td>{{b+1}}. {{anwr}} </td>
                <td>
                    <div class="custom-checkbox custom-control">
                        <input type="checkbox" class="custom-control-input"
                            id="q{{question.qNo}}_{{b}}" value="{{b+1}}"
                            formControlName="{{anwr}}"
                            [ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && formSubmitted}" />
                        <label class="custom-control-label"
                            for="q{{question.qNo}}_{{b}}"></label>
                    </div>
                </td>
            </tr>
            <div class="text-danger"
                *ngIf="surveyQuestionForm.get('q'+ question.qNo).hasError('atLeastOneRequired') && formSubmitted">
                At least One Answer required</div>
        </table>

    </div>
</div>

again, use the formGroupName directive since your overall form is just a group of groups, then within, your formControlNames are the answers themselves.

now your multiple choice binding, generally the same issues:

<div class="form-group" formGroupName="{{'q' + question.qNo}}">

and

<input type="number" style="width:40px;"
    id="q{{question.qNo}}_{{a}}"
    [ngClass]="{'is-invalid': surveyQuestionForm.get('q'+ question.qNo).errors 
    && surveyQuestionForm.get('q'+ question.qNo).touched}"
    formControlName="{{anwr}}"
    class="text-center" />

finally, your free text answer, basically the same problems, need the formGroupName directive, and to bind correctly to the static answer control within it:

<td><textarea class="form-control" rows="5" id="comment" name="text"
    [ngClass]="{'is-invalid':surveyQuestionForm.get('q'+ question.qNo).errors && 
    surveyQuestionForm.get('q'+ question.qNo).touched}"
    formControlName="answer"></textarea></td>

fixed blitz:

https://stackblitz.com/edit/angular-d4p6ly?file=src/app/app.component.html

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

11 Comments

I changed everything like you said,but it's showing validation error only second question
That’s because of how you’ve defined your error messages. You need to look at what I’ve showed you and figure out why they’re wrong or ask a different question about why they’re not showing correctly. I’m not doing anything else considering I’ve answered 2 full fledged questions and not gotten as much asan upvote
I'm extremely sorry about it.I marked as my previous question as answered one.I'll marked this answer also the correct one.Please tell me what is the issue of this validation message not showing?
I highly appreciate your effort.But I'm not well in English and waited to marked these two questions as correct answered you posted together.That's why I didn't do that.So sorry about it
You need to recognize that your form is a group of form groups and make sure you’re checking the errors on the correct forms... in almost all your errors (except the group ones) you need to add .get(‘answer’) or .get(anwr) depending on the type to make it read correctly
|
0

You have nested formgroups, so in the template you should add formgroupname to them. I don't know if it intentional or not, you can see it on the console. I've changed you stackblitz.

https://angular-kdhmha.stackblitz.io

1 Comment

thanks alot for your answer.It's only show validation error in second question

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.