1

I am trying to populate nested ReactiveForm which is split into several child form components. Once the API request is completed I am able to populate parent form but cannot loop child FormArray exactly to the count of child data. Below are the snippets from my code:

Edit View: edit.component.ts

@Component({
  selector: 'app-edit',
  templateUrl: './edit.component.html',
  styleUrls: ['./edit.component.scss']
})
export class EditComponent implements OnInit {

  public data: Service = new Service({
            id: '',
            title: '',
            description: '',
            service_extras: []
        });    
  public routeParams: any = {};    
  constructor ( 
    private route: ActivatedRoute,
    private service: ServicesService
  ) { }

  ngOnInit() {
    this.setRouteParams();
  }

  setRouteParams() {
    this.route.params.subscribe(params => {
        this.routeParams = params;
      // getting services using api call
        this.getService(this.routeParams);
    });
  }

  getService(params) {
    this.service
    .getService(params.id)
    .subscribe((service: Service) => {
        this.data = service;
    });
  }

}

I am requesting data in base edit.component.ts component and passing the data received to the parent form component which is child of EditComponent

edit.component.html

<service-form [serviceFormData]="data"></service-form>

service-form.component.ts

@Component({
    selector: 'service-form',
    templateUrl: './service-form.component.html',
    styleUrls: ['./service-form.component.scss']
})
export class ServiceFormComponent implements OnInit {

    private _serviceFormData = new BehaviorSubject<Service>(null);    
    @Input()
    set serviceFormData(value) {
        this._serviceFormData.next(value);
    }        
    get serviceFormData() {
        return this._serviceFormData.getValue();
    }    
    public service: Service;
    public serviceForm: FormGroup;

    constructor(
        private fb: FormBuilder
    ) { }

    ngOnInit() {    
        this.serviceForm = this.toFormGroup();
        this._serviceFormData.subscribe(data => {
            this.serviceForm.patchValue(data);
        });
    }

    private toFormGroup(): FormGroup {           
        const formGroup = this.fb.group({
            id: [ '' ],
            title: [ '' ],
            description: [ '' ]
        });    
        return formGroup;
    }
}

I have here receiving data with the help of @Input var by subscribing to its changes and then patching value to the form, now everything works fine upto this as all the fields gets filled once the data is received. Problem is here below:

service-extra.component.ts

@Component({
  selector: 'service-extra-form',
  templateUrl: './service-extra.component.html',
  styleUrls: ['./service-extra.component.scss']
})
export class ServiceExtraComponent implements OnInit {    
  private _extraFormData = new BehaviorSubject<ServiceExtra[]>([]);

  @Input() serviceForm: FormGroup;    
  @Input() 
  set extraFormData(value: ServiceExtra[]) {
    this._extraFormData.next(value);
  }    
  get extraFormData() {
    return this._extraFormData.getValue();
  }

  public extrasForm: FormGroup;

  constructor(private fb: FormBuilder) { }

  ngOnInit()
  {
    this.serviceForm.addControl('service_extras', new FormArray([]));    
    this._extraFormData.subscribe(data => {
      this.addNew();
      this.extras.patchValue(data);
    });

  }

  private toFormGroup()
  {
    const formGroup = this.fb.group({
      id: [''],
      service_id: [''],
      title: ['']
    });    
    return formGroup;
  }

  public addNew()
  {
    this.extrasForm = this.toFormGroup();        (<FormArray>this.serviceForm.controls.service_extras).push(this.extrasForm);
  }

  public removeThis(i: number)
  {
    (<FormArray>this.serviceForm.controls.service_extras).removeAt(i);
  }


  get extras(): FormArray {
     return this.serviceForm.get('service_extras') as FormArray;
  }

}

Above ngOnInit code does nothing but adds the single extras form (even if there are two records) and populate that form, this case is same even if I remove subscribe part and only use this.addNew(). How do I know how many records are there for ServiceExtras so that I can add that much FormGroups to FormArray.

Edit 1

Stackblitz Demo

Edit 2

Issue is if there are two records coming from API for service extra then I am not able to generate two FormGroups and populate them with data, Because at the time of rendering I cannot detect how many records are coming for service extras from API.

Sagar Guhe
  • 1,041
  • 1
  • 11
  • 35
  • Can you demonstrate your issue on stackblitz? – yurzui Apr 16 '18 at 04:55
  • @yurzui I have edited my question to add Stackblitz demo – Sagar Guhe Apr 16 '18 at 08:10
  • 1
    Check the code, but I still didn't understand what is the problem. WHEN do you to know how many records are there for ServiceExtras? – trungk18 Apr 16 '18 at 09:22
  • Do you want this counting? https://drive.google.com/file/d/1aTsX4pwRkLTntGJUWNhvpRlFqrTru12T/view?usp=drivesdk – Gauravbhai Daxini Apr 16 '18 at 09:43
  • @trungk18 actually the issue is if their are 2 records coming for service extra then it is populating only one and this happening because I am pushing 1 form group to the `service_extra` ``... My problem is I cannot detect how many service_extra records are coming from API so that I can generate those many `FormGroups` and and push is to `service_extras`, makes sense? – Sagar Guhe Apr 16 '18 at 09:59
  • so you want print this in addnew function? – Gauravbhai Daxini Apr 16 '18 at 10:11
  • You can get count of field like below: console.log(this.serviceForm.value.service_extras.length+1); – Gauravbhai Daxini Apr 16 '18 at 10:35
  • This doesn't solve my issue, please read my edit 2. – Sagar Guhe Apr 16 '18 at 10:43
  • @SagarGuhe please check this link may be helpful to you: https://stackoverflow.com/questions/43520010/angular-4-form-formarray-add-a-button-to-add-or-delete-a-form-input-row – Gauravbhai Daxini Apr 16 '18 at 10:52
  • @dgpoo buddy if you have seen my Stackblitz demo then you would know I have already achieved this... – Sagar Guhe Apr 16 '18 at 11:01

2 Answers2

2

In service-form.component.html, you bind serviceForm.value.service_extras property to extraFormData. But before service-extra-form component initialized serviceForm doesn't have service_extras formArray. And after init. of service-extra-form component, you call addRow() method which fill form with one row exactly:

service-extra.component.ts:

 ngOnInit()
  {
    this.serviceForm.addControl('service_extras', new FormArray([]));    
    this._extraFormData.subscribe(data => {
      console.log('service_extra_data', data);
      this.addNew();
      this.extras.patchValue(data);
    });

  }

service-form.component.html:

<service-extra-form [serviceForm]="serviceForm" 
                    [extraFormData]="serviceForm.value.service_extras">
 </service-extra-form> 

Passing [serviceForm] to service-extra-form is sufficent. Why you pass serviceForm.value.service_extras, if you already pass form?

Fix, removing extra сodes. Do the same with less code

ServiceExtraComponent:

export class ServiceExtraComponent implements OnInit {
  @Input() serviceForm: FormGroup;
  @Input() extraFormData: ServiceExtra[];

  public extrasForm: FormGroup;

  constructor(private fb: FormBuilder) { }

 ngOnChanges(data) {
    //console.log('ngOnChanges', data);

    // if changes only extraFormData
    if (data.extraFormData) {
      this.extras.setValue([]); // reset
      let newExtraArr = data.extraFormData.currentValue;

      for (let i = 0; i < newExtraArr.length; i++) {
        this.addNew();
        this.extras.patchValue(newExtraArr);
      }
    }
  } 

  ngOnInit() {
    this.serviceForm.addControl('service_extras', new FormArray([]));
  }

@Input() extraFormData: ServiceExtra[]; here pass extra_data from api and on ngOnChanges lyfecycle hook, resetting serviceForm's service_extras formArray

StackBlitz Demo

Yerkon
  • 4,548
  • 1
  • 18
  • 30
1

Here is a fix ( stackblitz here )

Explanation of the problematic behavior :

Your input is declared this way :

<service-extra-form [serviceForm]="serviceForm" [extraFormData]="serviceForm.value.service_extra"></service-extra-form>

So the input is the value of the form, not the value from the api.

But as you populate your form after an async call to the api, the first value "sent" to ServiceExtraComponent is undefined and in the ServiceExtraComponent ngOnInit you call

this.addNew();
this.extras.patchValue(data); 

with data = undefined

It creates a new formGroup in the formArray, then patch with undefined.

So when the API responds your form is already created with a formArray containing one item, so the patchValue truncate your service_extra array to match your form.

A correction could be to bind the return of the API directly as ServiceExtraComponent Input aka :

<service-extra-form [serviceForm]="serviceForm" [extraFormData]="serviceFormData.service_extras"></service-extra-form>
Pierre Mallet
  • 7,053
  • 2
  • 19
  • 30