0
votes

I am working on an Angular project using PrimeNG and I have the following problem to solve using reactive form.

Basically in my HTML I have something that rendering this:

enter image description here

Basically the interested field is the Commessa field representing a "project order ID". I think that sometimes it is used the WSS word (Work Breakdown Structure) in English-speaking business language.

And this is the code that I am using at the moment:

<div class="row">
  <div class="col-2">
    <p>Commessa</p>
  </div>
  <div class="col-10">
    <input id="commessa" class="p-invalid" aria-describedby="commessa-help" formControlName="commessa" type="text" pInputText required/>
    <i class="pi pi-plus-circle"></i>
  </div>
</div>

So when the form is "submitted" (it is not a real submission) in my TypeScript component code I have this orderForm object:

orderForm: FormGroup;

that is initialized into the ngOnInit() setting the validation rules for all the fields defined in my form:

ngOnInit() {
    this.orderFormValues = new OrderFormValues();

    console.log("orderFormValues VALUES: " + this.orderFormValues.statoOrdine);

    this.orderForm = this.fb.group({
    idOrdine: [null, [Validators.required]],
    dataInserimentoOrdine: [new Date(), [Validators.required]],
    statoOrdine: [null, [Validators.required, Validators.minLength(5)]],
    commessa: [null, [Validators.required, Validators.minLength(5)]],
    CIG: [null, [Validators.required, Validators.pattern("^[a-zA-Z0-9]{10}$")]],
    dataInizioAttivita: [null, [Validators.required]],
    dataFineAttivita: [null, [Validators.required]],
    referente: [null, [Validators.required]],
    ruoloReferente: [null, [Validators.required]],
    tipologiaDiPartecipazione: [null, [Validators.required, Validators.minLength(5)]],
    quotaPercentualeDiRTI: [null, [Validators.max(100)]],

    cliente: [null, [Validators.required]],
    vatCliente: [null, [Validators.required]],
    clienteFinale: [null, []],
    vatClienteFinale: [null, []],

    tipologiaContratto: [null, []],
    importoContratto: [null, [Validators.required]],
    linkContratto: [null, [Validators.required]],
    dataSottoscrizioneContratto: [null, [Validators.required]],

    nomeSocieta: [null, [Validators.required]],
    vatSocieta: [null, []],
    buName: [null, [Validators.required]],

    presenzaAQ: [false, [Validators.required]],
    linkIdentificativoAQ: [null, []],
    accordoQuadro: [null, []],
    residuoAccordoQuadro: [null, []],
    compagineDiAQ: [null, []]
});

Until now I only add a single "Commessa" field (because the use case foresaw a single ID order for the single order represented by my form). But now things are changed and I have to handle multiple ID orders for a single order. So I need n "Commessa" fields.

My original idea in fact was to add the + icon:

<i class="pi pi-plus-circle"></i>

Clicking this icon the FE will add a new field to insert a second, third,...n field for a new ID order.

The problem is how to handle validation and coordination with my reactive form in this case? In fact at the moment I have to statically define the fields that will be associated to my FormGroup object via formControlName attribute in the HTML.

In the specific case at the moment I have that:

 <input id="commessa" class="p-invalid" aria-describedby="commessa-help" formControlName="commessa" type="text" pInputText required/>

is related to the following FormGroup element using the formControlName="commessa"

commessa: [null, [Validators.required, Validators.minLength(5)]],

in this way when the user fill the Commessa field in the front end the commessa element in the FormGroup is valorized and it checks if the inserted value is valid or not.

So I was think to add a new input field clicking the + button (for example having id="commessa_2" and formControlName="commessa_2" but then how to handle this situation via backend? Is it possible to add a new element with its validator into my FormGroup object?

What could be a good strategy to handle this kind of dynamic situation when you don't know the exact number of fields in a form? (when some fields can be created dynamically as in this case).

How can I solve this problem in a smart way?

2
Read the docs: angular.io/api/forms/FormControl. Do some research.R. Richards

2 Answers

2
votes

Angular provides Form Array for this. So instead of creating multiple fields in the Form Group. You can set the commessa field as a Form Array. Which allows field to be dynamically inserted into it as an array.

1
votes

Using Reactive form FormArray, we can achieve the requirement.

Please refer to the below example enter image description here Example code:

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(private fb: FormBuilder) { }
  orderForm = this.fb.group({
    idOrdine: [null, [Validators.required]],
    commessaList: new FormArray([
      new FormControl('', [Validators.required, Validators.minLength(5)])
    ])
  });

  get commessaList(): FormArray {
    return this.orderForm.get('commessaList') as FormArray;
  }
  onFormSubmit(): void {
    for (let i = 0; i < this.commessaList.length; i++) {
      console.log(this.commessaList.at(i).value);
    }
  }
  addCommessaField() {
    this.commessaList.push(new FormControl('', [Validators.required, Validators.minLength(5)]));
  }

  deleteCommessaField(index: number) {
    if (this.commessaList.length !== 1) {
      this.commessaList.removeAt(index);
    }
    console.log(this.commessaList.length);
  }
}

app.component.html

<div class="container">
  <br>
  <form [formGroup]="orderForm" (ngSubmit)="onFormSubmit()">
    <div class="form-group row">
      <label for="idOrdine" class="col-sm-2 col-form-label">Id Ordine</label>
      <div class="col-sm-10">
        <input type="text" formControlName="idOrdine"
          [ngClass]="{'error':orderForm.controls.idOrdine.invalid && orderForm.controls.idOrdine.touched}"
          class="form-control" id="idOrdine">
      </div>
    </div>
    <div formArrayName="commessaList">
      <div class="form-group row">
        <label for="commessa" class="col-sm-2 col-form-label">Commessa</label>
        <div class="col-sm-10">
          <ng-container *ngFor="let commessa of commessaList.controls; index as idx">
            <div class="row">
              <div class="col-sm-8">
                <input type="text" [ngClass]="{'error':commessa.invalid && commessa.touched}" [formControlName]="idx"
                  class="form-control" id="commessa">
              </div>
              <div class="col-sm-2">
                <button type="button" *ngIf="idx===0" (click)="addCommessaField()" class="btn btn-success"
                  [ngClass]="'pad'"><i class="fa fa-plus-circle" aria-hidden="true"></i></button>

                <button (click)="deleteCommessaField(idx)" *ngIf="idx!==0" class="btn btn-danger">
                  <i class="fa fa-trash" aria-hidden="true"></i>
                </button>
              </div>
            </div>
          </ng-container>
        </div>
      </div>
    </div>
    <div>
      <button type="submit" class="btn btn-primary" [disabled]="orderForm.invalid">Save</button>
    </div>
  </form>
</div>

app.component.css

.container {
    margin: 100px;
}
.error{
    background-color: red;
    color:#FFF;
  }

  .row {
    display: flex;
    align-items: baseline;
    justify-content: center;
}