arraysangularfirebaseionic-framework

How can I fix this ERROR Error: Cannot find control with path: 'tituloQueHacer -> 6 -> descripcion'


I have make a form to edit the elements in the Collection "Cruceros" but if for example the array "tituloQueHacer" only have 6 elements in the array if I press the "+" button and try to add another one that is where the error shows up, maybe the problem is in "addTituloQueHacerButton()", this is my code:

edit-crucero.page.html

<ion-header>
  <ion-toolbar color="primary">
    <ion-buttons slot="start">
      <ion-back-button defaultHref="/gestor-cruceros"></ion-back-button>
    </ion-buttons>
    <ion-title>Editar Crucero</ion-title>
  </ion-toolbar>
</ion-header>

<ion-content>
  <form [formGroup]="editForm" (ngSubmit)="saveChanges()">
    <ion-item class="titulo" color="primary">
      <ion-title>Crucero</ion-title>
    </ion-item>
    <ion-item>
      <ion-input label="Nombre del Crucero:" [clearInput]="true" placeholder="Añade el nombre del Crucero" name="name" formControlName="name"></ion-input>
    </ion-item>

    <ion-item class="titulo" color="primary">
      <ion-title>Subtitulo</ion-title>
    </ion-item>
    <ion-item>
      <ion-input label="Subtitulo del Crucero" [clearInput]="true" placeholder="Añade el subtitulo del Crucero" name="subtitle" formControlName="subtitle"></ion-input>
    </ion-item>

    <ion-item class="titulo" color="primary">
      <ion-title>Imagen del Crucero</ion-title>
    </ion-item>
    <ion-item>
      <ion-input label="Imagen del Crucero" [clearInput]="true" placeholder="Añade la imagen del Crucero" name="imagenCrucero" formControlName="imagenCrucero"></ion-input>
    </ion-item>

    <ion-item class="titulo" color="primary">
      <ion-title>Descripción</ion-title>
    </ion-item>
    <ion-item>
      <ion-input label="Descripción del Crucero" [clearInput]="true" placeholder="Añade la descripción del Crucero" name="descripcion" formControlName="descripcion"></ion-input>
    </ion-item>
    
    <ion-item class="titulo" color="primary">
      <ion-title>Imagen de la Descripción</ion-title>
    </ion-item>
    <ion-item>
      <ion-input label="Imagen de la Descripción" [clearInput]="true" placeholder="Añade la imagen de la descripcion" name="imagenDescripcion" formControlName="imagenDescripcion"></ion-input>
    </ion-item>

    <ion-item class="titulo" color="primary">
      <ion-title>Título Que Hacer</ion-title>
    </ion-item> 
    <div formArrayName="tituloQueHacer">
      <ion-item *ngFor="let item of tituloQueHacer.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Título Que Hacer ' + (i + 1)" [clearInput]="true" placeholder="Añade el titulo de Que Hacer" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addTituloQueHacerButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Imagen Que Hacer</ion-title>
    </ion-item>
    <div formArrayName="imagenQueHacer">
      <ion-item *ngFor="let item of imagenQueHacer.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Imagen Que Hacer ' + (i + 1)" [clearInput]="true" placeholder="Añade la imagen de Que Hacer" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addImagenQueHacerButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Descripción Que Hacer</ion-title>
    </ion-item>
    <div formArrayName="descripcionQueHacer">
      <ion-item *ngFor="let item of descripcionQueHacer.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Descripción Que Hacer ' + (i + 1)" [clearInput]="true" placeholder="Añade la descripcion de Que Hacer" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addDescripcionQueHacerButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Título Camarotes</ion-title>
    </ion-item>
    <div formArrayName="tituloCamarotes">
      <ion-item *ngFor="let item of tituloCamarotes.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Titulo Camarotes ' + (i + 1)" [clearInput]="true" placeholder="Añade el titulo de Camarotes" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addTituloCamarotesButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Descripción Camarotes</ion-title>
    </ion-item>
    <div formArrayName="descripcionCamarotes">
      <ion-item *ngFor="let item of descripcionCamarotes.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Descripción Camarotes ' + (i + 1)" [clearInput]="true" placeholder="Añade la descripcion de Camarotes" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addDescripcionCamarotesButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Imagen Camarotes</ion-title>
    </ion-item>
    <div formArrayName="imagenCamarotes">
      <ion-item *ngFor="let item of imagenCamarotes.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Imagen Camarotes ' + (i + 1)" [clearInput]="true" placeholder="Añade la imagen de Camarotes" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addImagenCamarotesButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Imagen de Planos</ion-title>
    </ion-item>
    <div formArrayName="planos">
      <ion-item *ngFor="let item of planos.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Imagen Planos ' + (i + 1)" [clearInput]="true" placeholder="Añade la imagen de Planos" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addPlanosButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Lugares</ion-title>
    </ion-item>
    <div formArrayName="lugares">
      <ion-item *ngFor="let item of lugares.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Nombre Lugares ' + (i + 1)" [clearInput]="true" placeholder="Añade el nombre de Lugares" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addLugaresButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Precio Lugares</ion-title>
    </ion-item>
    <div formArrayName="precioLugares">
      <ion-item *ngFor="let item of precioLugares.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Precio Lugares ' + (i + 1)" [clearInput]="true" placeholder="Añade el precio de Lugares" formControlName="descripcion" type="number"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addPrecioLugaresButton()">+</ion-button>
    </div>

    <ion-item class="titulo" color="primary">
      <ion-title>Puertos</ion-title>
    </ion-item>
    <div formArrayName="puertos">
      <ion-item *ngFor="let item of puertos.controls; let i = index" [formGroupName]="i">
        <ion-input [label]="'Nombre Puertos ' + (i + 1)" [clearInput]="true" placeholder="Añade el nombre de Puertos" formControlName="descripcion"></ion-input>
      </ion-item>
    </div>
    <div class="titulo">
      <ion-button (click)="addPuertosButton()">+</ion-button>
    </div>

    <ion-button expand="full" type="submit" color="success">Guardar Cambios</ion-button>
  </form>
</ion-content>

edit-crucero.page.ts

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormArray, FormBuilder, FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { CrucerosService } from '../cruceros.service';

@Component({
  selector: 'app-edit-crucero',
  templateUrl: './edit-crucero.page.html',
  styleUrls: ['./edit-crucero.page.scss'],
  standalone: true,
  imports: [CommonModule, IonicModule, ReactiveFormsModule, RouterLink]
})
export class EditCruceroPage implements OnInit {

  cruceroId: string = "";
  crucero: any = {};
  editForm: FormGroup;

  constructor(
    private activatedRoute: ActivatedRoute,
    private crucerosService: CrucerosService,
    private router: Router,
    private fb: FormBuilder
  ) {
    this.editForm = this.fb.group({
      name: [''],
      subtitle: [''],
      imagenCrucero: [''],
      descripcion: [''],
      imagenDescripcion: [''],
      tituloQueHacer: this.fb.array([]),
      imagenQueHacer: this.fb.array([]),
      descripcionQueHacer: this.fb.array([]),
      tituloCamarotes: this.fb.array([]),
      descripcionCamarotes: this.fb.array([]),
      imagenCamarotes: this.fb.array([]),
      planos: this.fb.array([]),
      lugares: this.fb.array([]),
      precioLugares: this.fb.array([]),
      puertos: this.fb.array([])
    });
  }

  ngOnInit() {
    this.cruceroId = this.activatedRoute.snapshot.paramMap.get('id')!;
    this.loadCrucero();
  }

  addTituloQueHacerButton() {
    (this.editForm.get('tituloQueHacer') as FormArray).push(new FormControl(''));
  }

  getTituloQueHacerControls() {
    return (this.editForm.get('tituloQueHacer') as FormArray).controls;
  }

  addImagenQueHacerButton() {
    (this.editForm.get('imagenQueHacer') as FormArray).push(new FormControl(''));
  }

  getImagenQueHacerControls() {
    return (this.editForm.get('imagenQueHacer') as FormArray).controls;
  }

  addDescripcionQueHacerButton() {
    (this.editForm.get('descripcionQueHacer') as FormArray).push(new FormControl(''));
  }

  getDescripcionQueHacerControls() {
    return (this.editForm.get('descripcionQueHacer') as FormArray).controls;
  }

  addTituloCamarotesButton() {
    (this.editForm.get('tituloCamarotes') as FormArray).push(new FormControl(''));
  }

  getTituloCamarotesControls() {
    return (this.editForm.get('tituloCamarotes') as FormArray).controls;
  }
  addDescripcionCamarotesButton() {
    (this.editForm.get('descripcionCamarotes') as FormArray).push(new FormControl(''));
  }

  getDescripcionCamarotesControls() {
    return (this.editForm.get('descripcionCamarotes') as FormArray).controls;
  }
  addImagenCamarotesButton() {
    (this.editForm.get('imagenCamarotes') as FormArray).push(new FormControl(''));
  }

  getImagenCamarotesControls() {
    return (this.editForm.get('imagenCamarotes') as FormArray).controls;
  }
  addPlanosButton() {
    (this.editForm.get('planos') as FormArray).push(new FormControl(''));
  }

  getPlanosControls() {
    return (this.editForm.get('planos') as FormArray).controls;
  }
  addLugaresButton() {
    (this.editForm.get('lugares') as FormArray).push(new FormControl(''));
  }

  getLugaresControls() {
    return (this.editForm.get('lugares') as FormArray).controls;
  }
  addPrecioLugaresButton() {
    (this.editForm.get('precioLugares') as FormArray).push(new FormControl(''));
  }

  getPrecioLugaresControls() {
    return (this.editForm.get('precioLugares') as FormArray).controls;
  }
  addPuertosButton() {
    (this.editForm.get('puertos') as FormArray).push(new FormControl(''));
  }

  getPuertosControls() {
    return (this.editForm.get('puertos') as FormArray).controls;
  }

  get tituloQueHacer(): FormArray {
    return this.editForm.get('tituloQueHacer') as FormArray;
  }

  addTituloQueHacer(descripcion: string) {
    this.tituloQueHacer.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get imagenQueHacer(): FormArray {
    return this.editForm.get('imagenQueHacer') as FormArray;
  }

  addImagenQueHacer(descripcion: string) {
    this.imagenQueHacer.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get descripcionQueHacer(): FormArray {
    return this.editForm.get('descripcionQueHacer') as FormArray;
  }

  addDescripcionQueHacer(descripcion: string) {
    this.descripcionQueHacer.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get tituloCamarotes(): FormArray {
    return this.editForm.get('tituloCamarotes') as FormArray;
  }

  addTituloCamarotes(descripcion: string) {
    this.tituloCamarotes.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get descripcionCamarotes(): FormArray {
    return this.editForm.get('descripcionCamarotes') as FormArray;
  }

  addDescripcionCamarotes(descripcion: string) {
    this.descripcionCamarotes.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get imagenCamarotes(): FormArray {
    return this.editForm.get('imagenCamarotes') as FormArray;
  }

  addImagenCamarotes(descripcion: string) {
    this.imagenCamarotes.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get planos(): FormArray {
    return this.editForm.get('planos') as FormArray;
  }

  addPlanos(descripcion: string) {
    this.planos.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get lugares(): FormArray {
    return this.editForm.get('lugares') as FormArray;
  }

  addLugares(descripcion: string) {
    this.lugares.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get precioLugares(): FormArray {
    return this.editForm.get('precioLugares') as FormArray;
  }

  addPrecioLugares(descripcion: string) {
    this.precioLugares.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  get puertos(): FormArray {
    return this.editForm.get('puertos') as FormArray;
  }

  addPuertos(descripcion: string) {
    this.puertos.push(this.fb.group({
      descripcion: [descripcion]
    }));
  }

  async loadCrucero() {
    try {
      const cruceroData = await this.crucerosService.obtenerCruceros();
      this.crucero = cruceroData.find(c => c.id === this.cruceroId);
      if (this.crucero) {
        this.editForm.patchValue({
          name: this.crucero.name,
          subtitle: this.crucero.subtitle,
          imagenCrucero: this.crucero.imagenCrucero,
          descripcion: this.crucero.descripcion,
          imagenDescripcion: this.crucero.imagenDescripcion,
        });

        this.crucero.tituloQueHacer.forEach((titulo: string) => {
          this.addTituloQueHacer(titulo);
        });

        this.crucero.imagenQueHacer.forEach((imagen: string) => {
          this.addImagenQueHacer(imagen);
        });

        this.crucero.descripcionQueHacer.forEach((descripcion: string) => {
          this.addDescripcionQueHacer(descripcion);
        });

        this.crucero.tituloCamarotes.forEach((titulo: string) => {
          this.addTituloCamarotes(titulo);
        });

        this.crucero.descripcionCamarotes.forEach((descripcion: string) => {
          this.addDescripcionCamarotes(descripcion);
        });

        this.crucero.imagenCamarotes.forEach((imagen: string) => {
          this.addImagenCamarotes(imagen);
        });

        this.crucero.planos.forEach((planos: string) => {
          this.addPlanos(planos);
        });

        this.crucero.lugares.forEach((lugares: string) => {
          this.addLugares(lugares);
        });

        this.crucero.precioLugares.forEach((precioLugares: string) => {
          this.addPrecioLugares(precioLugares);
        });

        this.crucero.puertos.forEach((puertos: string) => {
          this.addPuertos(puertos);
        });
      }
    } catch (error) {
      console.error('Error loading crucero:', error);
    }
  }

  async saveChanges() {
    if (this.editForm.invalid) {
      console.log('Formulario inválido');
      return;
    }

    try {
      const formData = this.editForm.value;

      await this.crucerosService.actualizarCrucero(this.cruceroId, formData);
      
      this.router.navigate(['/gestor-cruceros']);
    } catch (error) {
      console.error('Error guardando cambios:', error);
    }
  }
}

Solution

  • When using a formGroup inside a formArray, you should define a wrapper element with the [formGroupName]="i" form group name having the index as a value. The controls of the formGroup should mimic the property names inside the formGroup you pushed.

    <div formArrayName="tituloQueHacer">
      <ion-item
        *ngFor="let control of getTituloQueHacerControls(); let i = index"
      >
        <div [formGroupName]="i">
          <ion-input
            [label]="'Título Que Hacer ' + (i + 1)"
            [clearInput]="true"
            placeholder="Añade el titulo de Que Hacer"
            formControlName="address"
          ></ion-input>
        </div>
      </ion-item>
    </div>
    

    Stackblitz Demo