Creating dynamic and responsive forms in Angular 8: Step-by-step guide

Recently, I posted a question regarding the dynamic generation of form controllers. However, I encountered some challenges with generating templates and controllers dynamically.

In this particular project, my main focus lies on handling four types of questions stored in an array. It is crucial to dynamically generate these questions based on their respective types:

  1. Multiple Choice Questions (MCQ) - Only one selection allowed

  2. Multiple Select Questions - Users can choose multiple answers with at least one required

  3. Ranking Questions - Users need to specify the correct order of answers, ensuring uniqueness

  4. Descriptive Questions - Allows users to provide their own answers

Below is the snippet of my HTML code:

<div class="container">
    <div class="row">
        ...
    </div>
</div>

Additionally, here is my TypeScript code:

surveyQuestionForm: FormGroup;
  formSubmitted = false;

  constructor(private fb: FormBuilder) { }
  questions: any = [
    ...
  ];

  ngOnInit() {
    this.createForms();
  }

  createForms(): any {
    ...
  }

  private buildSubGroup(question) {
    ...
  }

  atLeastOneRequired() {
    ...
  }

  uniqueNumbersValidator() {
    ...
  }

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

I've observed an error message stating "control.registerOnChange is not a function." You can access the StackBlitz link for more details: https://stackblitz.com/edit/angular-nya7l9

Your guidance and assistance in resolving this issue would be highly appreciated. Thank you!

Answer №1

First, the problem with constructing and associating your radio:

Construct it like so:

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

And associate it 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>

Utilize the formGroupName directive at the top then directly access the static answer control.

Next, onto your checkbox association:

<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, employ the formGroupName directive since your entire form consists of groups, and within them, your formControlNames represent the answers themselves.

Now onto your multiple-choice associations, facing similar 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" />

Lastly, addressing your free text response, encountering familiar issues requiring the formGroupName directive and accurate binding 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 solution:

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

Answer №2

When working with nested formgroups, make sure to include formgroupname in the template for clarity. It's important to note if this arrangement was intentional or not, as it can affect functionality. I have made some adjustments on your stackblitz.

Check out the updated version here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Typescript encounters issues when assigning declaration as TRUE

Currently, I'm working on a project in Angular 2 and attempting to create TypeScript definitions for it so that it can be exported as a library. I have various services set up that make HTTP requests to components, all structured similarly to the cod ...

Leveraging TypeScript alongside body-parser to access properties within req.body

I'm currently developing a web application using TypeScript and integrating the body-parser middleware to handle JSON request bodies. I've encountered type errors while attempting to access properties on the Request.body object. For instance, wh ...

What is the correct way to type "this" to ensure it works properly when called in a subclass method?

Let's consider including an extension method to the existing Object: declare global { interface Object { ext<B>(f: (x: this) => B): B; } } The aim is to apply it as shown below: "x".ext(x => x.toUpperCase()) //or (1).ext(x => ...

When the boolean is initially set to false, it will return true in an if statement without using

My Angular component contains a component-level boolean variable and an onClick event. Here's what the HTML file looks like: <div class="divClass" (click)="onClick($event)"></div> The relevant code from the TypeScript file is as follows: ...

Service call delay not displayed on screen

I have a function that I execute in my component's ngOnInit lifecycle hook. This function calls a service to fetch data and populate an array. Once that array is filled, I then call another service method using the data from the first array to populat ...

Creating TypeScript declarations for standard JavaScript functions and objects so they can be accessed in a TypeScript project

In my TS project, I am currently using Node, Express, and Handlebars along with some client-side JS successfully. I don't have any other client-side frameworks like React or Angular integrated at this time. Recently, I have been thinking about conver ...

The type 'IConnectionState' cannot be assigned to the type '{ connected: false; type: "none"; }'

My TypeScript linter seems to be short circuiting because I can't figure out why this linting error keeps coming up: Type 'IConnectionState' is not assignable to type '{ connected: false; type: "none"; }' Below is my code, whi ...

Create a placeholder for an item without the need for a specific function

Here is my current setup: sandbox.stub(rp, 'get').resolves(successResponse) This method provides a custom response when this line of code is executed: return await rp.get(url, options) However, I'm interested in achieving something like ...

What is the TypeScript definition for the return type of a Reselect function in Redux?

Has anyone been able to specify the return type of the createSelector function in Redux's Reselect library? I didn't find any information on this in the official documentation: https://github.com/reduxjs/reselect#q-are-there-typescript-typings ...

The issue arises when trying to access the 'addCase' property on the type 'WritableDraft<search>' within the builder argument of extra reducers

I am a beginner when it comes to using TypeScript with Redux Toolkit and I have encountered an issue with addCase not being available on the builder callback function in my extraReducers. I haven't been able to find a similar situation online, and I s ...

Sorting the material table based on the column IDs, which usually correspond to the column names, may not align with the properties of the data

.ts this.displayedColumns = [ { key: 'id', header: '#' }, { key: 'fullname', header: 'Full name' }, { key: 'email', header: 'email' }, { key: 'roleName', header: ...

When attempting to specify the path in the angular.json file, Angular encounters difficulty accessing Bootstrap from the node_modules directory

I have been attempting to integrate Bootstrap into my Angular project. Firstly, I used npm install --save bootstrap to add Bootstrap to my project. Following that, I installed jQuery as well. I then specified the path for Bootstrap. Displayed below is an ...

Incorporating Bootstrap Modal into a TypeScript-based minimalist web application

I want to incorporate the Bootstrap Modal component into my TypeScript application. Therefore, I added the necessary types: npm i @types/bootstrap After that, in my TypeScript code: import { Modal } from "bootstrap"; const myModal = new Modal(&a ...

Converting JSON into an interface in TypeScript and verifying its validity

How can I convert a JSON string to a nested interface type and validate it? Although my model is more complex, here is an example: export interface User = { name: Field; surname: Field; }; export interface Field = { icon: string; text: string; vis ...

Combining elements from a single array list and transferring them to another array list using Angular 4

In my arrayList called selectedSources, I have items such as: this.selectedSources.push( { id: 0, text: "A" }, { id: 1, text: "B" }, { id: 2, text: "C" }, { id: 3, text: "D"} ); The user has the option to select one or more of these items. When i ...

Struggling with loading an image in Angular 6 with cornerstonejs

Just dipping my toes into the world of Angular 6. I'm currently grappling with integrating cornerstonejs into my Angular 6 project. However, all my efforts seem to hit a snag with this error message: The dreaded error TS7016: Could not find a decla ...

Strategies for handling a collection of objects with immutability

Using TypeScript, I am trying to manage a list of objects without relying on ngrx and with immutability. As an example, this is how I'm approaching it: let items = <any>[]; let item1 = { n: 'toto' }; // ADD item1 items ...

Utilizing TypeScript to define the parameter of a method within a generic interface by extracting a value from the generic type

In search of defining a versatile interface that can manage any Data type, I came up with an idea. This interface includes a dataKey property which simply holds a value of keyof Data. Additionally, it features a handler function where the parameter type sh ...

The ultimate guide to loading multiple YAML files simultaneously in JavaScript

A Ruby script was created to split a large YAML file named travel.yaml, which includes a list of country keys and information, into individual files for each country. data = YAML.load(File.read('./src/constants/travel.yaml')) data.fetch('co ...

Toggle visibility of content with the click of a button

Hey there, fellow coder! I'm still getting the hang of Angular 2 and Typescript, so please be patient with me as I learn. I am working on a project where I have 5 buttons that should toggle the visibility of content, similar to a side menu. Below is ...