Rohan Sharma
Rohan Sharma

Reputation: 41

Create a dynamic angular form using the response from api (different form control type)

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
  }
]

Upvotes: 1

Views: 1863

Answers (2)

Camille
Camille

Reputation: 2531

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.

Upvotes: 1

Eliseo
Eliseo

Reputation: 57929

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>
      ...

Upvotes: 0

Related Questions