How do I set the values from a nested object on a formBuilder?

So, I have a JSON object:

product:{
            "proId":17,
            "cliId":1,
            "proCode":"test",
            "proNome":"test",
            "proDescription":"TESTE",
            "values":[
                          {"pfvId":38,
                           "proId":17,
                           "pfvQuantiMin":12,
                           "pfvQuantiMax":15,
                           "pfvValue":13.5},    
                           
                           {"pfvId":39,
                           "proId":17,
                           "pfvQuantiMin":11,
                           "pfvQuantiMax":14,
                           "pfvValue":23.5},                           
                           ]
         }

When I register a new product, I can create 1 or infinite values so I made a reactive form;

that’s ok… simple…

But now I have to make an edit form - I’ve made a modal and passaed product as a parameter on (click).

In this edit form my user can add a new values if it was needed

I pass the product as a parameter for that form but I was struggling in the values part of this form.

I need to repeat the form part according my param… if I have 5 values, I need to have a 5 subforms nested my product form.

–

I set the values on my formbuilder this way:

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, ViewController } from 'ionic-angular';
import { Validators, FormGroup, FormArray, FormBuilder } from '@angular/forms';
import { ProductService } from '../../providers/product-service';
import { Http , Headers} from '@angular/http';
/**
 * Generated class for the EditProductModalPage page.
 *
 * See http://ionicframework.com/docs/components/#navigation for more info
 * on Ionic pages and navigation.
 */
@IonicPage()
@Component({
  selector: 'page-edit-product-modal',
  templateUrl: 'edit-product-modal.html',
})
export class EditProductModalPage {
  
  product:any;
  public productForm: FormGroup;

  constructor(public navCtrl: NavController, public navParams: NavParams,public viewCtrl: ViewController, private formBuilder: FormBuilder, public http: Http, ) {
    this.product = navParams.get('product');

/*Here comes my first data, I can bind that on the form*/

      this.productForm = this.formBuilder.group({
      proCodigo: [this.product.proCodigo, [Validators.required, Validators.required]],
      proNome: [ this.product.proNome, [Validators.required, Validators.required]],
      proId: this.product.proId,
      cliId:1,
      proDescricao: [this.product.proDescricao, [Validators.required, Validators.required]],
      faixasValor: this.formBuilder.group([
        this.initFaixas(),
      ])
    });
  }

/*Here comes the VALUES data, I know that the [0] takes the first position in the array but I need to repeat that in the view based in how many VALUES my product have, I'm struggling here*/

  initFaixas() {
    return this.formBuilder.group({
      pfvQuantiMin:[this.product.faixasValor[0].pfvQuantiMin, [Validators.required, Validators.required]] ,
      pfvId:null,
      proId:null,
      pfvQuantiMax: [this.product.faixasValor[0].pfvQuantiMax, [Validators.required, Validators.required]] ,
      pfvValor: [this.product.faixasValor[0].pfvValor, [Validators.required, Validators.required]]
    });
  }

/*Here I create a new blank VALUES as the object I said before*/

   initFaixasNew(){
     return this.formBuilder.group({
      pfvQuantiMin:["", [Validators.required, Validators.required]] ,
      pfvId:null,
      proId:null,
      pfvQuantiMax:[ "", [Validators.required, Validators.required]] ,
      pfvValor: ["", [Validators.required, Validators.required]]
    });
   }

   addFaixa() {
    const control = <FormArray>this.productForm.controls['faixasValor'];
    control.push(this.initFaixasNew());
  }

  removeFaixa(i: number) {
    const control = <FormArray>this.productForm.controls['faixasValor'];
    control.removeAt(i);
  }

  closeModal(){
    this.viewCtrl.dismiss();
  }


/*REQUEST*/

   editProduct(productForm) {
    this.http.post(MYURL, (productForm.value))
    .map(res => res)
    .subscribe(data => console.log(data));
    this.closeModal();
  }
}

and here is my HTML

<ion-header>
  <ion-navbar color="secondary">
    <ion-title>{{product.proNome}}</ion-title>
      <ion-buttons end>
        <button ion-button (click)="closeModal()">
          <ion-icon name="close"></ion-icon>  
        </button>
      </ion-buttons>
  </ion-navbar>
</ion-header>

<ion-content padding>

    <ion-card>
    <ion-card-content>
            <form [formGroup]="productForm" novalidate>

       <ion-item>
          <ion-label floating>CĂłdigo Auxiliar</ion-label>
          <ion-input type="text" formControlName="proCodigo"></ion-input>
        </ion-item>


        <ion-item>
          <ion-label floating>Nome do Produto</ion-label>
          <ion-input type="text" formControlName="proNome"></ion-input>
        </ion-item>

        <ion-item>
          <ion-label floating>Descrição do produto</ion-label>
          <ion-input type="text" formControlName="proDescricao"></ion-input>
        </ion-item>

         <div formArrayName="faixasValor">
           
           <!--I NEED TO REPEAT THIS PART BASED ON HOW MANY JSON PARAMETER 'VALUES' MY OBJECT HAVE -->

          <ion-row style="border:1px solid #DBDBDB; border-radius: 5px; margin-top: 10px; padding: 3px;" *ngFor="let faixas of productForm.controls.faixasValor.controls; let i=index">
            <ion-row style="width: 100%; padding: 2%;">
              <ion-col><span>Faixa {{i + 1}}</span></ion-col>
             <ion-col>
              <button ion-button clear style="float: right;  margin-top: 10px;" *ngIf="productForm.controls.faixasValor.controls.length > 1" (click)="removeFaixa(i)">
                  <ion-icon name="close"></ion-icon>  
               </button>
             </ion-col>
            </ion-row>
            <ion-row [formGroupName]="i">
              <ion-col col-12 col-sm-9 col-md-6 col-lg-4 col-xl-4>
                <ion-item>
                  <ion-label floating>Quantidade MĂ­nima</ion-label>
                  <ion-input type="number" formControlName="pfvQuantiMin"></ion-input>
                </ion-item>
              </ion-col>
              <ion-col col-12 col-sm-9 col-md-6 col-lg-4 col-xl-4>
                <ion-item>
                  <ion-label floating>Quantidade Máxima</ion-label>
                  <ion-input type="number" formControlName="pfvQuantiMax"></ion-input>
                </ion-item>
              </ion-col>
              <ion-col col-12 col-sm-9 col-md-6 col-lg-4 col-xl-4>
                <ion-item>
                  <ion-label floating>Valor</ion-label>
                  <ion-input type="text" formControlName="pfvValor"></ion-input>
                </ion-item>
              </ion-col>
            </ion-row>
          </ion-row>
        </div>
        <ion-row>
         
          
           <ion-col><button ion-button color="primary" style="margin-top: 10px;"  (click)="addFaixa()" clear>+ Faixa de Valor</button></ion-col>
          <ion-col><button ion-button [disabled]="!productForm.valid" color="secondary" style="float: right;  margin-top: 10px;" type="submit"
              color="secondary" (click)="editProduct(productForm)">Salvar Edição</button></ion-col>
        </ion-row>

      </form>
      <hr>
    </ion-card-content>

    <pre>form value: <br>{{productForm.value | json}}</pre>
  </ion-card>
</ion-content>

Here some images to better understanding:

image

Can someone help me to make this loop and set the values dinamically on my formbuilder?

Can someone help me with that? Or can I made in another way?

Your question confuses me a bit, because I don’t understand what the size of your screen is. Suppose I have 20 values I need to enter. Does that really appear all on the same screen? The exact same page and layout as when I have 1 value, and when I have 200 values? I think you should wireframe what your pages look like, and decide which forms you need after that. It doesn’t sound as though you need one form. It sounds as though you need a service that stores the values of your object, and then different forms for different pages, with each page being used as required to obtain all the information of each value.

Well, let me do a deeper explanation…

So I have a list of products, each card is a product

Let’s focus in only one product…

image

Each product has this data division

image

This product data comes from a json like this

product:{
            "proId":17,
            "cliId":1,
            "proCode":"impHP001",
            "proNome":"Impressora HP",
            "proDescription":"Impressora multi-funcional HP, copia, escaneia, completa e multifuncional",
            "values":[
                          {"pfvId":38,
                           "proId":17,
                           "pfvQuantiMin":3,
                           "pfvQuantiMax":12,
                           "pfvValue":135.9 
                           },   
                           
                           {"pfvId":39,
                           "proId":17,
                           "pfvQuantiMin":2,
                           "pfvQuantiMax":10,
                           "pfvValue":122.8
                           },     
                           {"pfvId":39,
                           "proId":17,
                           "pfvQuantiMin":1,
                           "pfvQuantiMax":13,
                           "pfvValue":108.50
                           },                           
                       ]
         }

Right? That’s the easy part.

Now, let’s go to the second part of my problem
I’ve made a reactive form that register that, in this form I can add how many values object that I need or want. That was easy…

Now I have to edit this PRODUCT like the print and the JSON… So I click on the edit button

image

When I click on the edit button it casts a modal with a form. The form is similar to the create form, the difference here is that I have to pass my PRODUCT as a parameter to fill all the fields.

Here is my modal:

and here is how it works

So, now, what is wrong? in the values[] area as you can see above in blue I call the the FIRST inner object only, but I need all of the three… That behavior will happen based in how many objects I will have in values[] .

But I don’t know how to make this loop to repeat the blue part based on my parameter.

To populate the fields I use this:

export class EditProductModalPage {
  
  product:any;
  public productForm: FormGroup;

  constructor(public navCtrl: NavController, public navParams: NavParams,public viewCtrl: ViewController, private formBuilder: FormBuilder, public http: Http, ) {
    this.product = navParams.get('product'); /*GETTING THE PARAM FROM CLICK*/

      this.productForm = this.formBuilder.group({

    /*INITIALIZING THE RED FIELDS*/

      proCodigo: [this.product.proCodigo, [Validators.required, Validators.required]],
      proNome: [ this.product.proNome, [Validators.required, Validators.required]],
      proId: this.product.proId,
      cliId:1,
      proDescricao: [this.product.proDescricao, [Validators.required, Validators.required]],
      values: this.formBuilder.array([
        this.initFaixas(),

     /*CALLING THE VALUES [ ] ARRAY*/
 
      ])
    });
  }

/*AQUI EU INSTANCIO OS DADOS QUE PRECISO REPETIR*/

  initFaixas() {
    return this.formBuilder.group({
      pfvQuantiMin:[this.product.values[0].pfvQuantiMin, [Validators.required, Validators.required]] ,
      pfvId:null,
      proId:null,
      pfvQuantiMax: [this.product.values[0].pfvQuantiMax, [Validators.required, Validators.required]] ,
      pfvValor: [this.product.values[0].pfvValor, [Validators.required, Validators.required]]
    });
  }

and the HTML of my form

            <form [formGroup]="productForm" novalidate>

       <ion-item>
          <ion-label floating>CĂłdigo Auxiliar</ion-label>
          <ion-input type="text" formControlName="proCodigo"></ion-input>
        </ion-item>


        <ion-item>
          <ion-label floating>Nome do Produto</ion-label>
          <ion-input type="text" formControlName="proNome"></ion-input>
        </ion-item>

        <ion-item>
          <ion-label floating>Descrição do produto</ion-label>
          <ion-input type="text" formControlName="proDescricao"></ion-input>
        </ion-item>

         <div formArrayName="values">
           
           <!--HERE STARTS THE BLUE FIELDS OF THE IMAGE ABOVE -->

          <ion-row style="border:1px solid #DBDBDB; border-radius: 5px; margin-top: 10px; padding: 3px;" *ngFor="let faixas of productForm.controls.values.controls; let i=index">
            <ion-row style="width: 100%; padding: 2%;">
              <ion-col><span>Faixa {{i + 1}}</span></ion-col>
             <ion-col>
              <button ion-button clear style="float: right;  margin-top: 10px;" *ngIf="productForm.controls.values.controls.length > 1" (click)="removeFaixa(i)">
                  <ion-icon name="close"></ion-icon>  
               </button>
             </ion-col>
            </ion-row>
            <ion-row [formGroupName]="i">
              <ion-col col-12 col-sm-9 col-md-6 col-lg-4 col-xl-4>
                <ion-item>
                  <ion-label floating>Quantidade MĂ­nima</ion-label>
                  <ion-input type="number" formControlName="pfvQuantiMin"></ion-input>
                </ion-item>
              </ion-col>
              <ion-col col-12 col-sm-9 col-md-6 col-lg-4 col-xl-4>
                <ion-item>
                  <ion-label floating>Quantidade Máxima</ion-label>
                  <ion-input type="number" formControlName="pfvQuantiMax"></ion-input>
                </ion-item>
              </ion-col>
              <ion-col col-12 col-sm-9 col-md-6 col-lg-4 col-xl-4>
                <ion-item>
                  <ion-label floating>Valor</ion-label>
                  <ion-input type="text" formControlName="pfvValor"></ion-input>
                </ion-item>
              </ion-col>
            </ion-row>
          </ion-row>
        </div>
        <ion-row>
          
           <ion-col><button ion-button color="primary" style="margin-top: 10px;"  (click)="addFaixa()" clear>+ Faixa de Valor</button></ion-col>
          <ion-col><button ion-button [disabled]="!productForm.valid" color="secondary" style="float: right;  margin-top: 10px;" type="submit"
              color="secondary" (click)="editProduct(productForm)">Salvar Edição</button></ion-col>
        </ion-row>

      </form>

I know that the [0] takes only the first object of the array, but I need to implement all of them… how do I make that?

Is your card potentially infinite in size? You don"t know ahead of time how many values there are, right? So how do you know how to render the card? Do you seriously want a card with 500 columns, or whatever, if the number of values is 500?

1 Like

Yeah, that sounds weird but that’s exactly what I need. How to dinamically create this form part based on my parameter… specially this part

 initFaixas() {
    return this.formBuilder.group({
      pfvQuantiMin:[this.product.values[0].pfvQuantiMin, [Validators.required, Validators.required]] ,
      pfvId:null,
      proId:null,
      pfvQuantiMax: [this.product.values[0].pfvQuantiMax, [Validators.required, Validators.required]] ,
      pfvValor: [this.product.values[0].pfvValor, [Validators.required, Validators.required]]
    });
  }

I’m sure that we will not have 500, no more than 10… but if the user wants to make 15… 20… well… I have to permit that.

So, how do I repeat that based on the parameter? Getting rid of the [0] on the code above?

Your situation sounds to me very similar to the “secret lairs” of this guide.

2 Likes

I will look and try it

Thank you, that solved my problem, I’ve just adjust some parts, but that helped a lot!

Fixed something for me also. :raised_hands::pray::handshake: