arraysangularfirebaseionic-frameworkangular-reactive-forms

Edit "Cruceros" with tituloQueHacer returning key-value pair: "descripcion: value" instead of string in the array


I don't know why is this happening and I have been trying a lot of things but I can't fix this problem. The problem is for example if I edit my "Crucero" and change tituloQueHacer from "hola" to "adios" at first in the FireStore it was like this:

This is what I want

But then when I edit it it shows like this:

This is what I have

And I dont want to have the "descripcion" thing there

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(this.fb.group({
      descripcion: ['']
    }));
  }

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

  addImagenQueHacerButton() {
    (this.editForm.get('imagenQueHacer') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

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

  addDescripcionQueHacerButton() {
    (this.editForm.get('descripcionQueHacer') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

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

  addTituloCamarotesButton() {
    (this.editForm.get('tituloCamarotes') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  getTituloCamarotesControls() {
    return (this.editForm.get('tituloCamarotes') as FormArray).controls;
  }
  addDescripcionCamarotesButton() {
    (this.editForm.get('descripcionCamarotes') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  getDescripcionCamarotesControls() {
    return (this.editForm.get('descripcionCamarotes') as FormArray).controls;
  }
  addImagenCamarotesButton() {
    (this.editForm.get('imagenCamarotes') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  getImagenCamarotesControls() {
    return (this.editForm.get('imagenCamarotes') as FormArray).controls;
  }
  addPlanosButton() {
    (this.editForm.get('planos') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  getPlanosControls() {
    return (this.editForm.get('planos') as FormArray).controls;
  }
  addLugaresButton() {
    (this.editForm.get('lugares') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  getLugaresControls() {
    return (this.editForm.get('lugares') as FormArray).controls;
  }
  addPrecioLugaresButton() {
    (this.editForm.get('precioLugares') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  getPrecioLugaresControls() {
    return (this.editForm.get('precioLugares') as FormArray).controls;
  }
  addPuertosButton() {
    (this.editForm.get('puertos') as FormArray).push(this.fb.group({
      descripcion: ['']
    }));
  }

  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);
    }
  }
}

cruceros.service.ts

import { inject, Injectable } from "@angular/core";
import { addDoc, collection, doc, Firestore, getDoc, getDocs, orderBy, query, setDoc } from "@angular/fire/firestore";

@Injectable({
    providedIn: 'root',
})
export class CrucerosService {
    private firestore = inject(Firestore);
    public idCrucero: string = "";

    async guardarCrucero(
        idCrucero: any, 
        name: any, 
        subtitle: any, 
        imagenCrucero: any, 
        descripcion: any, 
        imagenDescripcion: any, 
        tituloQueHacer: any[],
        imagenQueHacer: any[],
        descripcionQueHacer: any[],
        tituloCamarotes: any[],
        descripcionCamarotes: any[],
        imagenCamarotes: any[],
        planos: any[],
        lugares: any[],
        precioLugares: any[],
        puertos: any[]
    ) {
        const obj ={
            "idCrucero" : idCrucero,
            "name" : name,
            "subtitle" : subtitle,
            "imagenCrucero" : imagenCrucero,
            "descripcion" : descripcion,
            "imagenDescripcion" : imagenDescripcion,
            "tituloQueHacer" : tituloQueHacer,
            "imagenQueHacer" : imagenQueHacer,
            "descripcionQueHacer" : descripcionQueHacer,
            "tituloCamarotes" : tituloCamarotes,
            "descripcionCamarotes" : descripcionCamarotes,
            "imagenCamarotes" : imagenCamarotes,
            "planos" : planos,
            "lugares": lugares,
            "precioLugares": precioLugares,
            "puertos": puertos
        };

        const Ref = collection(this.firestore, 'Cruceros');
        const docRef = await addDoc(Ref, obj);
        this.idCrucero = docRef.id;
        console.log(this.idCrucero)
    }

    async obtenerCruceros(){
        const Ref = collection(this.firestore, 'Cruceros');
        const q = query(Ref, orderBy('name'));
        const querySnapshot = await getDocs(q);
        const cruceros = querySnapshot.docs.map(doc => ({ id:doc.id, ...doc.data()}));
        return cruceros
    }

    async actualizarCrucero(id: string, formData: any) {
        const cruceroRef = doc(this.firestore, 'Cruceros', id.toString()); 
        await setDoc(cruceroRef, formData, { merge: true });
    }

    async obtenerCruceroPorId(id: string) {
        const Ref = doc(this.firestore, 'Cruceros', id);
        const docSnap = await getDoc(Ref);
        if (docSnap.exists()) {
          return docSnap.data();
        } else {
          console.log("No such document!");
          return null;
        }
      }
}

Solution

  • Solution 1

    Since you are constructing an array of FormGroup instance for tituloQueHacer, you can transform the objects to string by extracting descripcion before sending the request.

    async saveChanges() {
      ...
    
      try {
        const formData = this.editForm.value;
    
        // Transform objects to strings
        formData.tituloQueHacer = formData.tituloQueHacer.map((x: any) => x.descripcion);
    
        await this.crucerosService.actualizarCrucero(this.cruceroId, formData);
    
        this.router.navigate(['/gestor-cruceros']);
      } catch (error) {
        console.error('Error guardando cambios:', error);
      }
    }
    

    Solution 2

    Otherwise, you need to change the tituloQueHacer structure to a FormArray of FormControl(s) instead of FormGroup(s)

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

    And some changes to make when manipulating the control in the tituloQueHacer FormArray.

    addTituloQueHacer(descripcion: string) {
      this.tituloQueHacer.push(this.fb.control(descripcion));
    }
    
    addTituloQueHacerButton() {
      (this.editForm.get('tituloQueHacer') as FormArray).push(
        this.fb.control('')
      );
    }
    

    Demo @ StackBlitz