Search code examples
formsangulartypescriptdynamicapi-design

Populate dynamic forms with default data from api in Angular2


I have been working with angular2 for a few weeks now and am trying to create dynamic forms for a system settings page. The goal: have the api return any number of forms, build the forms inside tabs, forms can have any type of field, and populate those forms with default or previously saved data to then modify and save.

What works: Creating any number of forms with any number of input fields. I need help with populating form with data. Here is the items that are generated from the api call. the data that needs to populate these felids i need a separate api call. keep in mind each form can have multiple of each of these inputs

<li [formGroup]="form" class="form-control-item">
<div [ngSwitch]="item.controlType">
    <div *ngSwitchCase="'text'">
        <label class="text-lable " [for]="item.key">{{item.label}}</label>
        <div [ngSwitch]="item.controlType">
            <input [formControlName]="item.key" [id]="item.key" [type]="item.type" class="validate form-control input-lg" input name="{{ name }}" ng-model="name">
        </div>
    </div>
    <div *ngSwitchCase="'checkbox'">
        <label class="label-checkbox" [for]="item.key">{{item.label}}
            <input [formControlName]="item.key" name="{{ item.key }}" ng-model="name" type="checkbox" class="validate form-control checkbox-with-label" >
        </label>
    </div>
    <!--Needs testing with appropriate data-->
    <!--<div *ngSwitchCase="'dropdown'">-->
        <!--<label [for]="item.key">{{item.label}}</label>-->
        <!--<input [formControlName]="item.key" [id]="item.key" [type]="item.type" class="validate form-control input-lg">-->
        <!--<select [id]="item.key" [formControlName]="item.key">-->
            <!--<option *ngFor="let opt of item.options" [value]="opt.key">{{opt.value}}</option>-->
        <!--</select>-->
    <!--</div>-->
    <div *ngSwitchCase="'radio'">
        <label [for]="item.key">{{item.label}}</label>
        <input [formControlName]="item.key" [id]="item.key" [type]="item.type" class="validate form-control radio" name="{{ item.key }}" ng-model="name">
    </div>
    <div *ngSwitchCase="'password'">
        <label [for]="item.key">{{item.label}}</label>
        <input [formControlName]="item.key" [id]="item.key" [type]="item.type" class="validate form-control input-lg" name="{{ item.key }}" ng-model="name">
    </div>
</div>
<div class="errorMessage" *ngIf="!isValid">{{item.label}} is required</div>

Here is the component that calls the above:

@Component({
selector: 'dynamic-form',
templateUrl: 'dynamic-form.component.html',
styleUrls: ['./dynamic-form.component.scss']
})

export class DynamicFormComponent implements OnChanges {
    @Input() items: DynamicFormItemBase<any>[] = [];
    form: FormGroup;
    payLoad = '';

constructor(){
}

ngOnChanges(){
    this.form = this.toFormGroup(this.items);
}
ngOnInit(){
    this.form = this.toFormGroup(this.items);
}

onSubmit() {
    this.payLoad = JSON.stringify(this.form.value);
}

private toFormGroup(items: DynamicFormItemBase<any>[] ) {
    let group: any = {};
    items.forEach(item => {
        if(item.value.items){
            // console.log('has an internal item');
        }
        group[item.key] = new FormControl(item.value || '', item.required ? Validators.required : null);
        // console.log(item.value, 'has an internal item');
    });
    return new FormGroup(group);
}
}

so again to clarify I make an api call to get how many forms there will be and their names. Then an api call to get the layout of each form by name (from the first api call). all forms build OK. i then have a third api call that returns the config data for that that form; I need to bind this data to each specific form.

I cannot create the typical ng-model="name" name="ng-model" #name way of data binding. or at least I don't know how to implement it dynamically.

 <tab class="{{module.name}}-tab" *ngFor="let module of Modules; let i = 

index" heading="{{module.displayName}}" (select)="beforeChange(module.name)">
 <div class="dynamic-form" > 
<dynamic-form [items]="items" >Loading...</dynamic-form> 
</div>

Where items is the return from the api. its structured like so: First call is like this

ModuleList  [{"name":"moduleName","displayName":"Module Name","enabled":true}]

some other unrelated code runs and then makes an api call with a parameter of the maduleName to return the layout

Array[
    0:Array[
        0:DynamicFormItemBase[
        controlType:"text"
        key:"inputID"
        label:"Input Id"
        order:1
        required:true
        value:ConfigItem [
            displayName:"Input ID"
            items:undefined
            name:"inputID"
            required:true
            type:"text"]
        ]
    ]

The thirst API call (after the form is built) will call a different api method with the same parameter of moduleName, and return an object like this

Object
    inputID:""
    inputID:1
    inputID:""
    inputID:"default text"
    enabled":true

Where each inputID is actually the label ID from the second api call, and the enabled:true should target the Module (current stored in the session storage) from the first api call (if the data is different I will need to update the stored item).

Where the api: items gets passed into the dynamic-form component

<tab class="{{module.name}}-tab" *ngFor="let module of dpsModules; let i = index"
                  heading="{{module.displayName}}" (select)="beforeChange(module.name)">
                <div class="dynamic-form"  >
                    <dynamic-form [items]="items"  >Loading...</dynamic-form>
                </div>
            </tab>

Solution

  • Is what I am gathering from your question is correct, the data you get from the third API should have the values that you want to apply to the form group that you constructed from the information you got in your second API call.

    If this is correct, then you need to iterate through the results from the third API call and apply the values to the form group (that should be built by the time you start this).

    I am assuming looping/iterating in JavaScript/Typescript is something that you are familiar with.

    I am also going to assume that the data from the third API call has a field, or value, that will allow you to identify the form control that it's value needs to be assigned to. For this demonstration, I am going to use item.key as the linking token.

    Here is the pseudo code for what you need to attempt:

    for each item.key 
      this.form.controls[item.key].value = item.value;
    

    this.form is something you already have in your component. You use the controls collection in it, along with an indexing value (item.key in this case), to read or assign the value of the control.

    If you do not have something that can provide the link between the form group and the data in the third API call, binding them could prove difficult, if not impossible. If you have something more than just a key, like a key and description, you can use that with some simple concatenation.

    I hope this helps you out!