1

I have a feature where I need to send dynamically generated input fields on button click.

I have recreated the problem on stackblitz for better understanding.

In that app, when I enter resourceQuantity, the resourceId fields are dynamically generated. My issue is to identify those fields individually and send them on server side on single button click.

This solution I've found on stackblitz is similar, but in my problem I am not removing or adding on button clicks, but (change) event instead.

Here is the HTML code:

<mat-form-field>
    <input  matInput type="number" formControlName="resourceQuantity" [(ngModel)]="resourceQuantity" placeholder="Enter Resource Quantity" (change)="somethingChanged()"/> 
</mat-form-field><br/><br/>
<div>
    <ul>
        <li *ngFor="let item of counter(resourceQuantity)">
            <input  matInput type="number" placeholder="Enter Resource Number" formControlName="resourceId"/> 
        </li>       
    </ul>
</div>

And here is the TS code:

  ngOnInit() {
    this.form = new FormGroup({
            'employeeId': new FormControl(null, {validators: [Validators.required]}),
            'employeeName': new FormControl(null, {validators: [Validators.required]}),
            'resourceQuantity': new FormControl(null, {validators: [Validators.required]}),
            'resourceId': new FormControl(null, {validators: [Validators.required]})
    });
  }

  somethingChanged() {
      console.log(this.resourceQuantity);
  }

  counter(i: number) {
      return new Array(i);
  }

Kindly let me know the best solution to my problem. Thanks.

Akber Iqbal
  • 14,487
  • 12
  • 48
  • 70
Rachit
  • 79
  • 1
  • 12
  • You mean to say you want something like this: {empId:1, empName:"abc", resourceQuantity:2, resourceId:11, resourceId:22} ? @Rachit – Saima Haji Jul 05 '19 at 05:40

4 Answers4

2

Avoid using ngModel and formControl together. You can make use of formArray along with getter functions in your component to get your result.

Edit: I have edited my code to reflect the change you requested by subscribing to valueChanges on the resourceQuantity formControl and generating the formArray when it detects the change. This creates the resources in real time.

Note: Don't forget to unsubscribe from valueChanges to prevent memory leakage. I have updated my Stackblitz to show the same.

Here is a working example on StackBlitz

nash11
  • 8,220
  • 3
  • 19
  • 55
  • Thanks @nash11, it works but the input fields are not generating if I click the top and down arrows of matInput. – Rachit Jul 05 '19 at 07:02
  • Check out the stackblitz again. I have edited my code :) – nash11 Jul 05 '19 at 07:12
  • Thank you so much, Nash! Also, I didn't try the array approach because the resourceIds are supposed to be unique at DB level. So, I have made a separate table to achieve that. – Rachit Jul 05 '19 at 07:21
1

What you have here is a perfect use case for formArray... the only difference is that you need to add the formControls on the basis on the value which is entered in the resourceQuantity field.

relevant HTML:

<mat-card>
    <form [formGroup]="form" (submit)="add()">
        <mat-form-field>
            <input matInput type="number" formControlName="employeeId" placeholder="Enter Employee Id" (change)='updateFormString()'/>
      </mat-form-field><br/><br/>
      <mat-form-field>
            <input matInput formControlName="employeeName" placeholder="Enter Employee Name" (change)='updateFormString()'/>
      </mat-form-field><br/><br/>
      <mat-form-field>
            <input  matInput type="number" formControlName="resourceQuantity" [(ngModel)]="resourceQuantity" placeholder="Enter Resource Quantity" (change)="somethingChanged()"/> 
        </mat-form-field><br/><br/>
           <!--
             <div>
                <ul>
                    <li *ngFor="let item of counter(resourceQuantity)">
                        <input  matInput type="number" placeholder="Enter Resource Number" formControlName="resourceId"/> 
                    </li>       
                </ul>
            </div>
            -->
        <div fxLayout>
            <div>
                <button
                    mat-raised-button
                    color="accent" [disabled] = "form.invalid">Save
                </button>
            </div>
        </div>

        <div formArrayName='resourceId'>
          <br/>
          <div *ngFor='let item of resourceId.controls; let i = index'>
            <input type='text' [formControlName]="i" >
          </div>
        </div>

    </form>
</mat-card>

{{formString}}

relevant TS:

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormArray, FormBuilder } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  form: FormGroup;
  resourceQuantity: any;
  formString: string;

  constructor(private fb: FormBuilder) { }

  ngOnInit() {
    this.form = this.fb.group({
      'employeeId': new FormControl(null, { validators: [Validators.required] }),
      'employeeName': new FormControl(null, { validators: [Validators.required] }),
      'resourceQuantity': new FormControl(null, { validators: [Validators.required] }),
      //'resourceId': new FormControl(null, {validators: [Validators.required]}),
      resourceId: this.fb.array([
        this.fb.control('test entry default')
      ])
    });
  }

  updateFormString() {
    this.formString = JSON.stringify(this.form.value);
  }

  somethingChanged() {
    for (var i = 1; i < this.resourceQuantity; i++) {
      this.addResource();
    }
    this.updateFormString();
  }

  get resourceId() {
    return this.form.get('resourceId') as FormArray;
  }

  addResource() {
    this.resourceId.push(this.fb.control('test entry additional'));
  }

  counter(i: number) {
    return new Array(i);
  }
}

working stackblitz available here

Akber Iqbal
  • 14,487
  • 12
  • 48
  • 70
1

You should use FormArray to solve this issue. StackBlitz Demo

1.Change your resourceId from FormControl to FormArray as Follows.

 'resourceId': new FormArray([])

2.Change your counter method to push FormControl in your FormArray based on the Resource Quantity, this method calls when the change event triggers in the Resource Quantity. It gets the resourceQuantity value from the FormGroup, then clears the FormArray value. After that, it loops over the index to dynamically create FormControl and push it in FormArray

counter() {
    const index = parseInt(this.form.value.resourceQuantity);
    (this.form.controls.resourceId as FormArray).clear();
    for(let i = 0; i < index; i++) {
      const formControl = new FormControl();
      (this.form.controls.resourceId as FormArray).push(formControl);
    }
}

3.use getters to access the controls easily

  get formControls(): any {
    return this.form.controls;
  }

  get resourceIDControls(): any {
    return this.form.controls.resourceId['controls'];
  }

4.Change your HTML to loop over FormArray and set the FormControlName dynamically.

<div formArrayName="resourceId">
     <ul>
         <li *ngFor="let item of resourceIDControls; let i = index">
             <input  matInput type="number" placeholder="Enter Resource Number" [formControlName]="i"/> 
         </li>       
     </ul>
 </div>

Sample Inputs - Demo

Form Value after the submission will look like this

Nithya Rajan
  • 4,722
  • 19
  • 30
1

I would also set up my employee model to include a Resource array;

import { Resource } from './resource.model';

export interface Employee {
    employeeId: number;
    employeeName: string;
    resourceQuantity: number;
    resourceIds: Resource[];
}

Then your resource just needs a id:

export interface Resource {
    resourceId: number;
}
Kevin McCann
  • 511
  • 5
  • 13