1

I am dynamically creating checkboxes and associated dropdowns from the data i get from api ....

my html code below::

<label [for]="i" class="form-check-label fw7 h5 mb0" formArrayName="planDivList" *ngFor="let plan of planForm.controls.planDivList.controls; let i = index">
    <br>
    <input [name]="i" [id]="i" class="form-check-input" type="checkbox" [formControlName]="i">
    {{planDivList[i].planCode}}
    <label *ngIf="planDivList[i].divisions.length > 0" for="inputDiv">
        Divisions
        <select id="inputDiv" formcontrolName='divCtrl'>
            <option *ngFor="let division of planDivList[i].divisions" Value="division.divisionCode">
                {{division.divisionName}}
            </option>
        </select>
    </label>
</label>

my dataset is

planDivList = [
    { planCode: "B3692", divisions: [] },
    { planCode: "B3693", divisions: [] },
    { planCode: "B67", divisions: [{ divisionCode: "2", divisionName: "Assisted Living " }, { divisionCode: "1", divisionName: "LILC" }] },
    { planCode: "B69", divisions: [{ divisionCode: "3", divisionName: "Four Seasons" }, { divisionCode: "2", divisionName: "Lakeside" }, { divisionCode: "1", divisionName: "Sunrise" }] }
];

ts file:

const selectedPlans = this.planForm.value.planDivList
          .map((checked, index) => (checked ? this.planDivList[index].planCode : null))
          .filter(value => value !== null);

console.log(selectedPlans);

Here is my stackblitz

https://stackblitz.com/edit/angular-fsgswa?file=src%2Fapp%2Fapp.component.ts

i am able to get the selected checkbox value.But How do i get the value of the respective division that is selected for that checkbox in submit button? Any help is appreciated

Margerine
  • 183
  • 1
  • 4
  • 14

1 Answers1

1

To solve your problem, I have take several steps, which I will break down below.

1. Build your form array

You have an array of data for which each item should have multiple controls (checkbox and select). This requires a form array of form groups.

Your form is an array and nothing more, so we can simply bind the form to an array.

We will need to refer to the nested form control values later, so store them in a property for easy reference.

form: FormArray;
private formControls: {
  checkbox: FormControl;
  division: FormControl;
}[];

// initialise component in ngOnInit instead of constructor
ngOnInit() {    
  this.formControls = this.plans.map((plan, i) => {
    const divisionCode = plan.divisions.length > 0 
      ? plan.divisions[0].divisionCode 
      : '';
    return { 
      checkbox: this.fb.control(i === 0),
      division: this.fb.control(divisionCode)
    };
  });

  const formGroups = this.formControls.map(x => this.fb.group(x));
  this.form = this.fb.array(formGroups, this.minSelectedCheckboxes(1));
}

2. Bind the HTML to the form

You should prefer handling form submit by using (submit) to a button (click). (submit) will handle all of the ways in which a form can be submitted.

The HTML bindings match the structure of the form we built. Notice how the checkbox and division controls are binding to the ith form group in the array.

I would also recommend using your model as much as possible to generate loops, and restrict form binding to the form control directives.

<form [formGroup]="form" (submit)="onSubmit()">    
  <div *ngFor="let plan of plans; index as i" [formGroupName]="i">
    <label>
      <input type="checkbox" formControlName="checkbox">
      {{plan.planCode}}
    </label>

    <label *ngIf="plan.divisions.length > 0" >
      Divisions
      <select formControlName="division">
        <option *ngFor="let division of plan.divisions"   
          [value]="division.divisionCode">
          {{division.divisionName}}
        </option>
      </select>
    </label>
  </div>  
  <div *ngIf="formInvalid && form.hasError('required')">
    At least one plan must be selected
  </div>
  <button>Enroll</button>
</form>

3. Handle the submit

It is now simply a case of querying the form control array to find out which ones are checked, and what the selected division code is where relevant.

onSubmit(){
  this.formInvalid = this.form.invalid;    
  if (this.formInvalid) {
    return;
  }

  this.selectedPlans = this.plans
    .map((plan, i) => ({
      planCode: plan.planCode,
      selected: this.formControls[i].checkbox.value,
      divisionCode: this.formControls[i].division.value
    }))
    .filter(x => x.selected);
}

The actual output that you need will probably differ slightly from my example, and I renamed a few of your properties for the purposes of a simpler demo. Hopefully I have demonstrated the techniques that you can apply to your own form.

DEMO: https://stackblitz.com/edit/angular-qxm8wk

Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40
  • Thank you so much for taking the time to explain in detail. You saved my day. I am really new to Angular and learning. One more question.. If i have other fields in the same form Like first name ,date of birth etc? how do i add the validations to these fields in the form array like required field ? and how to get their values on submit??? Thanks a lot again. – Margerine Mar 11 '20 at 14:05
  • No problem. Adding other controls is simple. You will need to go back to using a form group as your top-level element. Then add other controls / arrays as necessary. I've forked my demo: https://stackblitz.com/edit/angular-tbtmaf – Kurt Hamilton Mar 11 '20 at 14:21
  • Again, just demonstrating the concepts - you will want to tidy up at least the HTML as you see fit – Kurt Hamilton Mar 11 '20 at 14:22
  • Actually after u added the firstname validation .. the plans error is not firing "At least one plan must be selected".. Any idea why? – Margerine Mar 11 '20 at 15:24
  • Ah yeah, I broke the validation query in the HTML. I've fixed it, although I wouldn't be happy with leaving it like this in my own project - I'd rather keep all of my form queries strongly typed, but hopefully you get the idea of how it all works and come up with your own strategy – Kurt Hamilton Mar 11 '20 at 15:28
  • Sure . Thanks so much! – Margerine Mar 11 '20 at 15:42
  • Hi Kurt I am running into a another issue if i point to the data from api..if you have few mins ..i posted the question here: https://stackoverflow.com/questions/60677297/cannot-find-control-with-path-plans-0 – Margerine Mar 13 '20 at 22:01
  • Hi Kurt.. Sorry for the trouble. My data changed. How do i map the plan plans: this.plans.map((plan, i) => { const divisionCode = plan.divisions.divisions.length > 0 ? plan.divisions.divisions[0].divisionCode : ''; if the divisions is an array by itself. My sample data is here https://stackblitz.com/edit/angular-fsgswa?file=src%2Fapp%2Fapp.component.ts – Margerine Mar 16 '20 at 22:26
  • @Margerine So now you have arrays of arrays? I can't answer this - it's your data - you know how it should be mapped. This is your stackblitz modified to use the first level of divisions, but you probably want to use all divisions. It's one thing to know javascript, and another thing to know your data structures and know your requirements. https://stackblitz.com/edit/angular-6agpja – Kurt Hamilton Mar 17 '20 at 07:43
  • oh ok makes sense. but i think now i understand better with your examples. Thanks. I really appreciate your time . – Margerine Mar 17 '20 at 13:19