Search code examples
javascriptangulartypescriptangular-directivedynamic-arrays

How can I generate FormControls dynamically inside a FormGroup?


I am receiving an object from the db that contains a few elements with the following format:

info = [{
    idcentro: "8227",
    namecentro: "Centro Paris", 
    address: "C/ Paris, 127",
    dias: [
      {
        dia: "0",
        horafinal: "06:00",
        horainicio: "17:00",
        salas: [
          {
            id: 0,
            nombre: "sala 1",
            intervalos: [
                ["09:00", "12:30", "10"],
                ["13:00", "21:30", "20"]
            ]
          }
        ]
      },
      {
        dia: "1",
        horafinal: "09:00",
        horainicio: "16:30",
        salas: [
          {
            id: 0,
            nombre: "sala 1",
            intervalos: [
                ["09:00", "12:30", "10"]
            ]
          },
          {
            id: 1,
            nombre: "sala 2",
            intervalos: [
                ["09:00", "20:30", "20"]
            ]
          }
        ]
      },
      {
        dia: "2",
        horafinal: "09:00",
        horainicio: "20:30",
        salas: [
          {
            id: 0,
            nombre: "sala 1",
            intervalos: [
                ["09:00", "12:30", "10"],
                ["12:45", "18:30", "15"]
            ]
          },
          {
            id: 1,
            nombre: "sala 2",
            intervalos: [
                ["09:00", "20:30", "20"]
            ]
          }
        ]
      }
    ]
  }];

To work with those "dias" array and include inputs to modify each value in case the user wants to, I've manually created a FormGroup containing a formControl for each of those days (the real object contains up to 8 days).

    this.openingHoursForm = new FormGroup({
      day0OpeningTime: new FormControl(""),
      day0ClosingTime: new FormControl(""),
      day1OpeningTime: new FormControl(""),
      day1ClosingTime: new FormControl(""),
      day2OpeningTime: new FormControl(""),
      day2ClosingTime: new FormControl(""),
      day3OpeningTime: new FormControl(""),
      day3ClosingTime: new FormControl(""),
      day4OpeningTime: new FormControl(""),
      day4ClosingTime: new FormControl(""),
      day5OpeningTime: new FormControl(""),
      day5ClosingTime: new FormControl(""),
      day6OpeningTime: new FormControl(""),
      day6ClosingTime: new FormControl(""),
      day7OpeningTime: new FormControl(""),
      day7ClosingTime: new FormControl(""),
      day8OpeningTime: new FormControl(""),
      day8ClosingTime: new FormControl("")
    });

And then, in the HTML, I am creating two inputs for each of those those formControlNames:

<div class="dayOpeningHours" title="">
  <div>Day 0</div>
   <div class="opens">
     <label for="">De:
       <input class="form-control" type="time" formControlName="day0OpeningTime" name="day0OpeningTime">
     </label>
   </div>
   <div class="closes">
     <label for="">A:
       <input class="form-control" formControlName="day0ClosingTime" name="day0ClosingTime">
     </label>
   </div>
</div>

As you can see, this is becoming very unmaintainable. I have tried to create the markup (in the html) and formControlNames (in the ts) dynamically by looping through each "dia" of the 'dias' array, but I haven't achieved anything.

Is there a way I can generate the HTML and the declaration of those Form Controls inside the same formGroup? I have been reading about FormArray and AbstractControl but I can not really understand how to apply that to my code.

Can someone help me out or give me some tips to find the right documentation?

Thank you


Solution

  • The better option would be to use a FormArray instead. You can also inject FormBuilder from @angular/forms

    See Below

    constructor (private fb: FormBuilder) { }
    diasForm = this.fb.group({
      openingHours: this.fb.array([])
    })
    get openingHours (): FormArray {
      return this.diasForm.get('openingHours') as FormArray;
    }
    
    

    The next task will be to create the form Lets assume we have dias as an array

    dias = [
          {
            dia: "0",
            horafinal: "06:00",
            horainicio: "17:00",
          },
          {
            dia: "1",
            horafinal: "06:00",
            horainicio: "17:00",
          },
          ...
    ]
    
    

    I have removed other properties for simplicity In the ngOnInit we can set the value of our form

    ngOnInit() {
      this.dias.forEach(dia => {
        this.openingHours.push(
          this.fb.group({
            horafinal: [dia.horafinal, []],
            horainicio: [dia.horainicio, []]
          })
        )
      })
    }
    
    

    In the Html

    <form [formGroup]='diasForm'>
      <div formArrayName='openingHours'>
        <div *ngFor='let item of openingHours.controls; let i = index' 
           [formGroupName]='i'>
             <input formControlName='horafinal' />
             <input formControlName='horainicio' />
        </div>
      </div>
    </form>
    

    The above should match the structure of our formGroup

    diasForm( [formGroup] ) ---> openingHours(formArray) ---> i ( [formGroup] ) ---> horafinal(formControl)

    Please see this demo on stackblitz

    This way you can have a dynamic number of days