1

Here is my FormGroup:

this.productGroup = this.fb.group({
  name: ['', Validators.compose([Validators.required, Validators.maxLength(80)])],
  desc: ['', Validators.maxLength(3000)],
  category: ['', Validators.required]
  variants: this.fb.array([
    this.fb.group({
      type: '',
      options: ''
    })
  ])
});

I need to dinamically add type and options control fields after the user click on a button. FormArray should look like this after User Input: [ {type: 'size', options: 'Small', 'Big'}, {type: 'color', options: 'red', 'blue, 'yellow'}, ... ].

Here whats I'm trying to do:

// Add new item to FormArray
addItem(): void {
  this.variantsArray = this.productGroup.get('variants') as FormArray;
  this.variantsArray.push(this.fb.group({
    type: '',
    options: ''
  }));
}

// Template
<form [formGroup]="productGroup">
  // inputs...
  <div formArrayName="variants" *ngFor="let item of productGroup.controls['variants']; let i = index;">
      <div [formGroupName]="i">
        <div class="row">
          <mat-form-field class="col-12">
            <input formControlName="type">
          </mat-form-field>
        </div>
        <div class="row">
          <mat-form-field class="col-12">
            <input formControlName="options">
          </mat-form-field>
        </div>
      </div>
      <div class="row">
        <a href="javascript:" (click)="addItem()"> Adicionar Variante </a>
        <a href="javascript:" (click)="removeItem(i)" *ngIf="i > 0"> Remover Variante </a>
      </div>
    </div>
</form>

How to make it work?

James
  • 1,189
  • 2
  • 17
  • 32
  • How can you store data like as you shown `{type: 'size', options: 'Small', 'Big'}` which is not even a valid javascript object like the property `options: 'Small', 'Big'`. Either options property should be an array else you will get the long string – Suryan Nov 01 '18 at 20:50
  • I'm going to use a string array to pass the data to this FormControl directly on my component. To be more precise, I'm using a `MatChipInput` to pass values to this string array. I'm trying to get a generic working solution for inputs so I can implement on my code. – James Nov 01 '18 at 20:58

2 Answers2

1

I don't know exactly what you want to achieve but I think I got your problem.

The following code:

variants: this.fb.array([
    this.fb.group({
      type: '',
      options: ''
    })
  ])

does not produce an array, so you can't iterate over it with *ngFor.

If you look a little deeper into it you will see that

productGroup.controls['variants']

has a property with the controls.

So just change the *ngFor to:

*ngFor="let item of productGroup.controls['variants'].controls; let i = index;"

and you should be fine.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
  • Thanks! `Angular Language Service` extension doesn't load properly some properties of Form Groups. Also let's say I'm passing a string array directly to `options` (I'm using some 'Chips' to add strings)... how to make sure I'm adding the string array to the right Form Array index? – James Nov 01 '18 at 21:08
  • If anyone can help me... made a new question: [link](https://stackoverflow.com/questions/53109833/angular-material-chips-how-to-use-it-on-a-dynamic-formarray) – James Nov 01 '18 at 21:50
0

For adding the formElemnt dynamically in the reactive form, you need to first create the form element or a form group and push it in the original formarry for that field.

EX: i have a customerForm like below

this.customerForm = this.fb.group({
  firstName: ['', [Validators.required, Validators.minLength(3)]],
  lastName: ['', [Validators.required, Validators.maxLength(50)]],
  emailGroup: this.fb.group({
    email: ['', [Validators.required, Validators.email]],
    confirmEmail: ['', Validators.required],
  }, { validator: emailMatcher }),
  phone: ''
  addresses: this.fb.array([this.createAddress()])
});

On click of add Address in View I can call a function in component that will push a new address to the addresses array in form.

  addAddress(): void {
   this.addresses.push(this.createAddress());
  }

 createAddress(): FormGroup {
   return this.fb.group({
      street1: ['', Validators.required],
      street2: '',
      city: '',
      state: '',
      zip: ''
   });
 }

In your view you can iterate over this address array and display the addresses. Initially it won't show as the array value will be empty! Hope it Helps.

nircraft
  • 8,242
  • 5
  • 30
  • 46
  • what if your FormArray doesn't have any FormGroup inside? – James Nov 02 '18 at 21:27
  • like this `addresses: this.fb.array([])` and you want to pass `[ 'string1', 'string2' ]` inside – James Nov 02 '18 at 21:37
  • In that case you can make use of the new FormControl() to create new controls on the form : Ex: this.customerForm.addControl('newControl', new FormControl(' ', Validators.required) ); – nircraft Nov 02 '18 at 21:51
  • could you take a look here? [Stackblitz](https://stackblitz.com/edit/angular-3od6rd) and [Question](https://stackoverflow.com/questions/53109833/angular-material-matchiplist-how-to-use-it-on-a-dynamic-formarray). I'm trying something like this to add these controls in a specific nested formgroup (group -> dynamic array -> group -> array): `this.productGroup.controls['variants']['options'].push(new FormControl(type));` – James Nov 02 '18 at 21:56
  • `this.productGroup = this.fb.group({ name: '', variants: this.fb.array([ this.fb.group({ type: '', options: this.fb.array([]) }) ]) })` – James Nov 02 '18 at 21:59
  • you may want to check this for any ideas: https://stackoverflow.com/a/53103747/9386929 – nircraft Nov 05 '18 at 14:02