1

I am new to angular and recently working on creating a dynamic form with validations from response given by api. The api will provide the form field like Date type input type or multi select. Also the api response will provide the validations like field is required or not and regex for the input field if exist.

Using all the above data I need to create a form in angular which should be working like a normal form with validations like touched, untouched, error on reuqired field, error on validators etc.

Public URL of the page created using html and javascript. The api will be available from network tabs

https://devemr.growthemr.com/assets/static/form.html?bid=1413&fid=9242

Below is the code base I tried.

form.component.ts

ngOnInit(): void {
    this.loadQuestions();  // it is the API call and I have attached the sample API response 
    this.form = this.fb.group({
      items: this.fb.array([])
    })
 }


  addItems() {

    this.contactFormQuestions.forEach((x: any) => {
      this.itemsFormArray.push(this.fb.group({
        [x.id]: ['', x.required ?[ Validators.required] : null, x.regex? [Validators.pattern(x.regex)]: ''],
      }));
    });

    console.log(this.form.get('items'));
  }

 get itemsFormArray() {
    return this.form.get('items') as FormArray;
  }

form.component.html

<form [formGroup]="form">
  <div formArrayName="items">
    <div *ngFor="let pqa of itemsFormArray.controls; let i = index">
      <div [formGroupName]="i">
        <div [ngSwitch]="pqa.value.questionType" class="switchCase">
          <div *ngSwitchCase="'Input'" class="input-feild">
            <input type="text" class="form-control" [formControlName]="pqa.value.id">
          </div>


          <div *ngSwitchCase="'Date'" class="input-feild">
            <input type="date" class="form-control" [formControlName]="pqa.value.id">
          </div>

        </div>

      </div>
    </div>
  </div>
</form>

API response

[
  {
    "id": 59233,
    "questionId": 74719,
    "questionName": "fname",
    "questionType": "Input",
    "hidden": null,
    "required": true,
    "validate": null,
    "regex": "[A-Za-z]",
    "validationMessage": null,
    "answer": false,
    "questionChoices": null,
    "patientQuestionChoices": [],
    "allowMultipleSelection": false,
    "showDropDown": null,
    "preSelectCheckbox": false
  },
  {
    "id": 59234,
    "questionId": 74720,
    "questionName": "Date",
    "questionType": "Date",
    "hidden": null,
    "required": true,
    "validate": null,
    "regex": null,
    "validationMessage": null,
    "answer": false,
    "questionChoices": null,
    "patientQuestionChoices": [],
    "allowMultipleSelection": false,
    "showDropDown": null,
    "preSelectCheckbox": false
  },
  {
    "id": 59235,
    "questionId": 74721,
    "questionName": "Multi Select",
    "questionType": "Multiple_Selection_Text",
    "hidden": null,
    "required": true,
    "validate": null,
    "regex": null,
    "validationMessage": null,
    "answer": false,
    "questionChoices": null,
    "patientQuestionChoices": [
      {
        "deleted": false,
        "tenantId": 1413,
        "id": 3993,
        "choiceName": "Option1",
        "choiceId": 8546,
        "selected": false
      },
      {
        "deleted": false,
        "tenantId": 1413,
        "id": 3994,
        "choiceName": "Option2",
        "choiceId": 8547,
        "selected": false
      }
    ],
    "allowMultipleSelection": true,
    "showDropDown": true,
    "preSelectCheckbox": false
  }
]
5
  • loadQuestions functions missing from you question. please add that in so we can see better whats going on. also i would advice not use use : any to cast all you types to something untyped. you will lose a lot of help from typescript doing that. Commented Jan 7, 2023 at 9:02
  • Load question is the api call and I have already attached the API response Commented Jan 7, 2023 at 9:07
  • yeah i see you call but not how you call it. Commented Jan 7, 2023 at 9:09
  • Its the same way we do api call in Angular ( A normal api call from service file using httpClient) loadQuestions() { this.questionarieService .getQuestions(45778, 9244) .then( (response: any) => { this.contactFormQuestions = response; this.addItems(); }) } Commented Jan 7, 2023 at 9:11
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. Commented Jan 7, 2023 at 11:04

2 Answers 2

1

Here you have some code to create a ReactiveForm. Maybe that can help you to solve your problem.

Possible way

Once you have multiple components for inputs form (text,date,multi-select,...), that should be possible to loop on your API response and test questionType to build a form dynamically (ReactiveForm in ts and inputs components in html).

Form component ts

// Reactive Forms
form: FormGroup;    

constructor(private formBuilder: FormBuilder) { }

ngOnInit() {
  // Form structure and validators
  this.form = this.formBuilder.group({
    'user' : this.formBuilder.group({
      'username' : ['', Validators.required],
      'email' : ['', [Validators.required, Validators.email]]
    }),
    'identity' : this.formBuilder.group({
      'firstname' : ['', Validators.required],
      'lastname'  : ['', Validators.required],
      'address' : this.formBuilder.group({
        'street' : ['', Validators.required],
        'city'  : ['', Validators.required],
      })
    })
  });        
}

onSubmit() {
    // Get object with same structure as form but only with values
    console.log(this.form.value);
    alert('Form is ' + (this.form.invalid ? 'invalid' : 'valid'));
}

Form component html

<form [formGroup]="form" (ngSubmit)="onSubmit()">
    <form-text [formGroupParent]="form.get(['user'])"
               [formGroupControlName]="'username'">
    </form-text>
    <form-text [formGroupParent]="form.get(['user'])"
               [formGroupControlName]="'email'">
    </form-text>
    <hr>
    <form-text [formGroupParent]="form.get(['identity'])"
               [formGroupControlName]="'firstname'">
    </form-text>
    <form-text [formGroupParent]="form.get(['identity'])"
               [formGroupControlName]="'lastname'">
    </form-text>
    <hr>
    <form-text [formGroupParent]="form.get(['identity','address'])"
               [formGroupControlName]="'street'">
    </form-text>
    <form-text [formGroupParent]="form.get(['identity','address'])"
               [formGroupControlName]="'city'">
    </form-text>
    <button type="submit">Submit</button>
</form>

Custom input component ts (form-text)

// Needed to bind formControlName
@Input() formGroupParent: FormGroup;
@Input() formGroupControlName: string;
// FormControl store validators
control: FormControl;

ngOnInit() {
    // Fetch Form control (validator) from FormGroup parent
    this.control = <FormControl>this.formGroupParent.get(this.formGroupControlName);
}

Custom input component html (form-text)

<ng-container [formGroup]="formGroupParent">
  <label>{{formGroupControlName}}</label> 
  <input type="text" formControlName="{{formGroupControlName}}">
</ng-container

External lib

For info (not always easy to integrate), a great library exists to design and manage survey : SurveyJS.

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

Comments

0

Take a look the docs

The idea is basically

1.-Create the formGroup

getForm(json:any){
  const group=new FormGroup({})

  json.forEach(x=>{
    const validators=[];
    if (x.required)
        validators.push(Validator.required)
    if (x.regex)
        validators.push(Validator.patern(x.regex)

    ....
    group.addControl(x.questionName,new FormControl("",validators)
  }
  return group
} 

2.-Iterate over form.controls.|keyvalue and crate the elements using the data in json

    <form [formGroup]="form">
       <div *ngFor="let keyvalue of form.controls|keyvalue;let i=index">
           <app-control [data]="json[i]" ></app-control>
       </div>
    </form>

Your app-control take a count the json data and use viewProviders. This make that you can use formControlName or formGroupName or ...

  @Component({
     selector: '..',
     templateUrl: '...'
     viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
  })

  @Input()data;

Your .html like

      <input *ngIf="data.questionType=='Input'"
             [formControlName]="data.questionName">
      <ng-container *ngIf="data.questionType=='Multiple_Selection_Text'">
        ...
      </ng-container>
      ...

Comments

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.