0

I have an API that returns the data something like but the class is same but number of entries changes.

HTML file:

<div class="col-12 col-md-6" formArrayName="plans">
  <div class="form-check" 
      *ngFor="let plan of plans; index as i" [formGroupName]="i">
    <label class="form-check-label fw7 h5 mb0">
      <input class="form-check-input" type="checkbox" formControlName="checkbox">
      {{plan.planCode}}
    </label>

    <label *ngIf="plan.divisions.length > 0">
      Divisions
      <select class="form-control" formControlName="division">
        <option *ngFor="let division of plan.divisions" [value]="division.divisionCode">
          {{division.divisionName}}
        </option>
      </select>
    </label>
  </div>         
</div>

TS file:

public plans: IPlan[] = new Array<Plan>();
public formControls: {
  firstNameCtrl: FormControl,    
  plans: {
    checkbox: FormControl;
    division: FormControl;
  }[]
};

ngOnInit() {
  this.restSvc.getData("/api/plan/GetPlanDetails")
    .subscribe((data => {
      if (!data.errorMessage) {
        this.plans = data;          
      } else {
        this.errMsg = data.errorMessage;
      }
    }), error => {
      this.errMsg = "We found some errors. Please review the form and make corrections.";
  });

  this.formControls = {
    firstNameCtrl: this.fb.control('', Validators.required),   
    plans: 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)
      };
    })
  };
}

I am getting error

Cannot find control with path: 'plans -> 0'

where the plan is getting mapped before the subscribe completes. How to resolve this?

Sample data:

plans = [ 
  { 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" } ] 
  } 
];
Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40
Margerine
  • 183
  • 1
  • 4
  • 14
  • This is most likely an error from your HTML, where one of your `formControl` directives points to something that doesn't exist. See: https://stackoverflow.com/a/39680433/1016004. Could you post the HTML of this component? – Robin De Schepper Mar 13 '20 at 22:27
  • Added html. Thanks – Margerine Mar 13 '20 at 22:43
  • Sample data: plans = [ { 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" } ] } ]; – Margerine Mar 13 '20 at 22:44
  • The problem is this line: `
    `. You don't have a formGroup "0" in plans: `this.formControls.plans` seems to be un uninitialized array. You also seem to have a bad habit of using the same generic variable name in multiple places. Try differentiating the multiple `plans` you use to maybe `planControls` and `plans`.
    – Robin De Schepper Mar 13 '20 at 22:48
  • Actually if i hard code the sample data directly instead from the api it works fine. – Margerine Mar 13 '20 at 22:55
  • Then there must be a difference between the data you expect to get and the data you're actually getting. The error itself can only mean 1 thing: the requested FormControl `[formGroupName]="0"` in `formArrayName="plans"` does not exist. – Robin De Schepper Mar 13 '20 at 23:00

1 Answers1

1

You need to build your form after your service returns the data.

Think of your ngOnInit function in steps.

  1. Request data
  2. Build form
  3. Build form array from empty plans array
  4. HTML is built

...

  1. Service returns data
  2. Update plans array
  3. HTML is updated

You now have a situation where *ngFor="let plan of plans" is creating a block of HTML for each plan, but you haven't added any form controls to your form.plans array. So when the form tries to bind to the first form control in form.plans, it doesn't find anything and breaks.

How to solve this?

The ultimate problem here is that your form array always needs to mirror the model you are building it from, otherwise the difference in array lengths is going to cause problems.

As far as I can tell, there isn't much point in you displaying a partial form while waiting for the service to return the plans. So I would recommend delaying the building of the form until you have all of the data. You would do this by building the form from inside the subscribe.

// no point in initialising with an empty array
plans: IPlan[];
formControls: {
  firstNameCtrl: FormControl,    
  plans: {
    checkbox: FormControl;
    division: FormControl;
  }[]
};

ngOnInit() {
  this.restSvc.getData("/api/plan/GetPlanDetails").subscribe(plans => {
    if (!plans.errorMessage) {
      this.plans = plans;
      this.buildForm();
    } else {
      this.errMsg = plans.errorMessage;
    }
  }, error => {
    this.errMsg = "We found some errors. Please review the form and make corrections.";
  });  
}

private buildForm(): void {
  this.formControls = {
    firstNameCtrl: this.fb.control('', Validators.required),   
    plans: 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)
      };
    })
  };

  // TODO: build the form here
}

You will now need to control when your form is added to the DOM, as you won't have a form for it to bind to when it is initially built.

<form *ngIf="form" [formGroup]="form">
  <!-- the form -->
</form>
Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40