En mi proyecto angular tengo un grid designer donde personalizo cada celda con otros componentes. 
Necesito que para que funcione correctamente, cuando se activa la multiselección, en el selectedContent se meta el valor del kpi por defecto como un array  con label y value de la misma forma que en contentOptions.

import { Component, OnInit, ViewContainerRef, ViewChildren, QueryList, AfterViewInit, NgModule, HostListener, ViewChild, ElementRef,inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { NgxSpinnerModule, NgxSpinnerService } from 'ngx-spinner';


import * as ace from 'ace-builds';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-dracula';

import { GaugeComponent } from '../gauge/gauge.component';
import { DataGridComponent } from '../data-grid/data-grid.component';
import { KpiListComponent } from '../kpis/kpi-list.component';
import { KpiService } from '../../services/kpi.services'
import { NgSelectModule } from '@ng-select/ng-select';

@Component({
  selector: 'app-grid-designer',
  standalone: true,
  imports: [CommonModule, FormsModule, NgSelectModule, NgxSpinnerModule],
  templateUrl: './grid-designer.component.html',
  styleUrl: './grid-designer.component.scss'
})
export class GridDesignerComponent implements OnInit, AfterViewInit {
  @ViewChildren('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainers!: QueryList<ViewContainerRef>;
  @ViewChild('contextMenu') contextMenuElement!: ElementRef;
  private kpiService = inject(KpiService);

  
  gridData: any[] = [];
  gridDataString: string = '';
  isHidden = false;
  jsonValid: boolean | null = null;
  private editor: ace.Ace.Editor | null = null;
  editingCell: any = null;
  contextMenuVisible = false;
  contextMenuX = 0;
  contextMenuY = 0;
  selectedCell: any = null;
  componentOptions: { label: string; value: any }[] = [];





  constructor(private http: HttpClient,
              private toastr: ToastrService,
              private spinner: NgxSpinnerService
  ) {}

  ngOnInit(): void {
    this.getListKPI()
    this.http.get('assets/grid-default.json').subscribe((data: any) => {
      this.gridData = this.initializeGridData(data);
      this.gridDataString = JSON.stringify(this.gridData, null, 2);
      this.updateEditorValue();
      this.configToastr();
    });
  }

  ngAfterViewInit(): void {
    this.initAceEditor();
    setTimeout(() => this.renderDynamicComponents(), 100);
  }

  getListKPI(): void {
    this.kpiService.getListKPI().subscribe(
      (data: any[]) => {        
        this.componentOptions = data.map(item => ({
          label: `kpi-${item.idKPI} - ${item.representacion} - ${item.nombre}`,
          value: item.idKPI
        }));                
      },
      (error: any) => {
        // Manejo de errores
      }
    );
  }
  

  onMultiselectionListChange(e:any, col:any){
  console.log("🔰 ~ onMultiselectionListChange ~ col:", col)
  console.log("🔰 ~ onMultiselectionListChange ~ e:", e)
  const { rowIndex, colIndex } = this.getCellIndices(col);
  this.renderDynamicComponents(rowIndex, colIndex);
  }
  
  getCellIndices(cell: any): { rowIndex: number, colIndex: number } {
    for (let i = 0; i < this.gridData.length; i++) {
      const row = this.gridData[i];
      const columns = row.columns;
      for (let j = 0; j < columns.length; j++) {
        if (columns[j] === cell) {
          return { rowIndex: i, colIndex: j };
        }
      }
    }
  
    return { rowIndex: -1, colIndex: -1 }; // Si no se encuentra
  }
  

  private initializeGridData(data: any[]): any[] {
    return data.map(row => ({
      ...row,
      columns: row.columns.map((col: any) => ({
        ...col,
        content: col.content || null,
        alternativeContent: null,
        showSwitch: false,        
        switchLeftText: 'Izquierda',  // texto por defecto
        switchRightText: 'Derecha',  // texto por edfecto
        switchActiveSide: 'left', // switch izquierdo por defecto
        multiSelection: false
      })),
      height: row.height || '20%',
    }));
  }

  private initAceEditor(): void {
    const editorElement = document.getElementById('editor');
    if (editorElement) {
      this.editor = ace.edit(editorElement);
      this.editor.setTheme('ace/theme/dracula');
      this.editor.session.setMode('ace/mode/json');
      this.editor.setOptions({
        fontSize: '14px',
        showPrintMargin: false,
        showGutter: true,
        highlightActiveLine: true,
        enableBasicAutocompletion: true,
        enableLiveAutocompletion: true,
        enableSnippets: true
      });

      this.updateEditorValue();

      this.editor.getSession().on('change', () => {
        this.gridDataString = this.editor?.getValue() ?? '';
        this.jsonValid=false;
      });
    } else {
      console.error('Error al inicializar el Editor Ace');
    }
  }

  onRightClick(event: MouseEvent, cell: any) {
    event.preventDefault();
    this.contextMenuVisible = true;
    this.contextMenuX = event.clientX;
    this.contextMenuY = event.clientY;
    this.selectedCell = cell;
  }

  @HostListener('document:click')
  onDocumentClick() {
    this.contextMenuVisible = false;
  }

  addColumnToRow() {
    if (this.selectedCell) {
      const rowIndex = this.gridData.findIndex(row => row.columns.includes(this.selectedCell));
      if (rowIndex !== -1) {
        const newColumn = { content: '', colSpan: 1, rowSpan: 1, isComponent: false, width: 'auto' };
        this.gridData[rowIndex].columns.push(newColumn);
        this.updateJsonAndEditor();
      }
    }
    this.contextMenuVisible = false;
  }

  changeRowHeight(event: Event, row: any) {
    const inputElement = event.target as HTMLInputElement;
    row.height = this.validateHeight(inputElement.value);
    this.updateJsonAndEditor();
  }

  private validateHeight(value: string): string {
    if (value.endsWith('px') || value.endsWith('%')) {
      return value;
    }
    const num = parseInt(value);
    if (isNaN(num)) return '20%';
    return `${Math.max(5, Math.min(100, num))}%`;
  }

  changeWidth(event: Event) {
    if (this.selectedCell) {
      const inputElement = event.target as HTMLInputElement;
      this.selectedCell.width = this.validateWidth(inputElement.value);
      this.updateJsonAndEditor();
    }
  }

  private validateWidth(value: string): string {
    if (value.endsWith('px') || value.endsWith('%')) {
      return value;
    }
    const num = parseInt(value);
    if (isNaN(num)) return '100%';
    return `${Math.max(10, Math.min(100, num))}%`;
  }

  adjustWidth(change: number) {
    if (this.selectedCell) {
      if (!this.selectedCell.width || this.selectedCell.width === 'auto') {
        this.selectedCell.width = '100%';
      }

      if (this.selectedCell.width.endsWith('px')) {
        const currentPx = parseInt(this.selectedCell.width);
        this.selectedCell.width = `${Math.max(50, currentPx + change)}px`;
      } else {
        const currentPercent = parseInt(this.selectedCell.width);
        this.selectedCell.width = `${Math.max(10, Math.min(100, currentPercent + change))}%`;
      }
      this.updateJsonAndEditor();
    }
  }

  adjustRow(row: any, change: number) {
    if (!row.height || row.height === 'auto') {
      row.height = '20%';
    }

    if (row.height.endsWith('px')) {
      const currentPx = parseInt(row.height);
      row.height = `${Math.max(50, Math.min(900, currentPx + change))}px`;
    } else {
      const currentPercent = parseInt(row.height);
      row.height = `${Math.max(5, Math.min(100, currentPercent + change))}%`;
    }
    this.updateJsonAndEditor();
  }


  getSelectedRow(): any {
    return this.gridData.find(row => row.columns.includes(this.selectedCell));
  }

  adjustColSpan(change: number) {
    if (this.selectedCell) {
      this.selectedCell.colSpan = Math.max(1, (this.selectedCell.colSpan || 1) + change);
      this.updateJsonAndEditor();
    }
  }

  adjustRowSpan(change: number) {
    if (this.selectedCell) {
      this.selectedCell.rowSpan = Math.max(1, (this.selectedCell.rowSpan || 1) + change);
      this.updateJsonAndEditor();
    }
  }

  deleteCell() {
    if (this.selectedCell) {
      const rowIndex = this.gridData.findIndex(row => row.columns.includes(this.selectedCell));
      if (rowIndex !== -1) {
        const columnIndex = this.gridData[rowIndex].columns.indexOf(this.selectedCell);
        this.gridData[rowIndex].columns.splice(columnIndex, 1);
        if (this.gridData[rowIndex].columns.length === 0) {
          this.gridData.splice(rowIndex, 1);
        }
        this.updateJsonAndEditor();
      }
    }
    this.contextMenuVisible = false;
  }

  
  addRow() {
    const newRow = {
      height: '100px',
      columns: [{ content: '', colSpan: 1, rowSpan: 1, isComponent: false, width: '100%' }]
    };
    this.gridData.push(newRow);
    this.updateJsonAndEditor();
  }

  addColumn() {
    this.gridData.forEach(row => {
      row.columns.push({ content: '', colSpan: 1, rowSpan: 1, isComponent: false });
    });
    this.updateJsonAndEditor();
  }

   updateJsonAndEditor() {
    this.gridDataString = JSON.stringify(this.gridData, null, 2);
    this.updateEditorValue();
    //setTimeout(() => this.renderDynamicComponents() );
  }

  private updateEditorValue(): void {
    if (this.editor && this.gridDataString) {
      this.editor.setValue(this.gridDataString);
      this.editor.clearSelection();
    }
  }


  configToastr(){
    this.toastr.toastrConfig.positionClass = 'toast-top-full-width';
    this.toastr.toastrConfig.extendedTimeOut = 0;
    this.toastr.toastrConfig.timeOut = 2900;
  }

  toggleSidebar() {
    this.isHidden = !this.isHidden;
}

  getGridTemplateColumns(columns: any[]): string {
    return columns.map(col => col.width).join(' ');
  }

  getGridTemplateRows(): string {
    return this.gridData.map(row => row.height).join(' ');
  }

  validateAndApplyJson() {
    try {
      const parsedData = JSON.parse(this.gridDataString);
      this.gridData = parsedData;
      this.jsonValid = true;
      setTimeout(() => this.renderDynamicComponents(), 100);
    } catch (error) {
      this.jsonValid = false;
      this.toastr.warning(`Error al procesar el JSON: ${error}`, 'Advertencia');
    }
  }

  renderDynamicComponents(specificRowIndex?: number, specificColIndex?: number): void {    
    console.log("🔰 ~ renderDynamicComponents ~ specificRowIndex:", specificRowIndex, "specificColIndex:", specificColIndex);
    setTimeout(() => {
      if (!this.dynamicComponentContainers) return;
  
      let containerIndex = 0;
      this.gridData.forEach((row, rowIndex) => {
        row.columns.forEach((col: any, colIndex: number) => {
          if (col.isComponent) {
            if (specificRowIndex === undefined || (rowIndex === specificRowIndex && colIndex === specificColIndex)) {
              const container = this.dynamicComponentContainers.get(containerIndex);
              if (container) {
                container.clear();
                this.createComponent(col.content, container, col, rowIndex, colIndex);
              }
            }
            containerIndex++;
          }
        });
      });
    }, 100);
  }

  
  
  

  async createComponent(componentName: string[], container: ViewContainerRef, col: any, rowIndex: number, colIndex: number): Promise<void> {
    console.log("🔰 ~ createComponent ~ col:", col)
    console.log("🔰 ~ createComponent ~ componentName:", componentName)
    this.spinner.show();
    try {
      const spinnerName = `spinner-${rowIndex}-${colIndex}`;
      this.spinner.show(spinnerName);
      

      // const kpiNumber = componentName[0]; // Extract the number from the array
      let kpiNumber;

      if (col.multiSelection) {
        // En multiselección, usamos el selectedContent
        kpiNumber = col.selectedContent.value;
        console.log("🔰 ~ createComponent ~ kpiNumber:", kpiNumber)
      } else {
        // Verificamos si el switch está activo y usamos el contenido correspondiente
        if (col.showSwitch) {
          if (col.switchActiveSide === 'left') {
            kpiNumber = col.content[0];
          } else {
            kpiNumber = col.alternativeContent[0];
          }
        } else {
          kpiNumber = col.content[0];
        }
      }
      console.log("🔰 ~ createComponent ~ kpiNumber:", kpiNumber)
  
  
      
      const ComponentType = await this.loadDynamicComponent(`kpi-${kpiNumber}`);
      const componentRef = container.createComponent(ComponentType);
  
      (componentRef.instance as any).kpiNumber = kpiNumber;
  
      // Verificamos si el componente tiene representación de tarjeta
      if ((componentRef.instance as any).isCard) {        
        col.isCard = true;
      } else {
        col.isCard = false;
      }
  
      this.spinner.hide(spinnerName);

    } catch (error) {
      this.spinner.hide(`spinner-${rowIndex}-${colIndex}`);

      this.toastr.error(`Error al crear el componente: ${componentName}`);
    } finally {
      // this.spinner.hide(); // ocultar el spinner
    }
      
  }
  

private async loadDynamicComponent(componentName: string): Promise<any> {
    const kpiNumber = parseInt(componentName.split('-')[1]);    
    const kpiRequest = {
        fechaDesde: "2023-01-01",
        fechaHasta: "2023-12-31",
        listKpi: [kpiNumber],
        sucursales: ""
    };
    
    const data = await this.kpiService.getKPI(kpiRequest).toPromise();
    console.log("🔰 ~ loadDynamicComponent ~ data:", data)

    let representacion: string | null = null;

    if (data.representacion) {
        representacion = data.representacion;
    }

    if (!representacion) {
        this.toastr.error(`No se encuentra "representacion" en el json para: ${componentName}`);
        throw new Error(`No se encuentra "representacion" en el json para: ${componentName}`);
    }

    const module = await import(`../kpi-components/kpi-${representacion}/kpi-${representacion}.component`);
    return module[`Kpi${this.pascalCase(representacion)}Component`];
}



  // convertir a pascalcase el string para que funcione
  private pascalCase(str: string): string {
    return str.split('-').map(part => part.charAt(0).toUpperCase() + part.slice(1)).join('');
  }

  
  editKPI(col: any) {
    console.log("🔰 ~ editKPI ~ col:", col)
    if (Array.isArray(col.content)) {
      col.content = col.content.map((item: number) => `kpi-${item}`).join(', ');
    } else if (typeof col.content !== 'string' || !col.content.startsWith('kpi-')) {
      col.content = null;
    }
    
    col.switchLeftText = col.switchLeftText || 'Izquierda';
    col.switchRightText = col.switchRightText || 'Derecha';
    this.editingCell = col;
  }
  
    toggleSwitch(col: any) {            
      console.log("🔰 ~ toggleSwitch ~ col:", col)
      if (!col.showSwitch) {
        col.alternativeContent = null;
      }
      this.updateJsonAndEditor();
    }

    toggleMultiSelection(col: any) {
      col.multiSelection = !col.multiSelection;
    
      if (col.multiSelection) {
        // Aseguramos que col.content es un array y si no, se transforma
        if (!Array.isArray(col.content)) {
          col.content = col.content ? [col.content] : [];
        }
    
        // Actualizamos contentOptions para que tenga solo las opciones seleccionadas
        col.contentOptions = this.componentOptions.filter(option => col.content.includes(option.value));
    
        // Ponemos el [0] de selectedContent como el primer KPI seleccionado por defecto
        col.selectedContent = col.content[0] || null;
      } else {
        // // Fuera de multiselección, nos aseguramos de que col.content sea un array con solo un elemento
        // if (col.content && col.content.length > 0) {
        //   col.content = [col.content[0]];
        //   col.selectedContent = col.content[0];
        // } else {
        //   col.content = [];
        //   col.selectedContent = null;
        // }
    
        // Limpiamos 'contentOptions' ya que no se necesita en modo no multiselección
        col.contentOptions = [];
      }
    
      console.log("Multiselección ", col);
    }
 
  
    switchComponent(col: any, side: string) {
      // Intercambiamos las propiedades relacionadas al contenido principal y alternativo
      [col.content, col.alternativeContent] = [col.alternativeContent, col.content];
      [col.contentOptions, col.alternativeContentOptions] = [col.alternativeContentOptions, col.contentOptions];
      [col.selectedContent, col.alternativeSelectedContent] = [col.alternativeSelectedContent, col.selectedContent];
    
      col.switchActiveSide = side;
    
      // Obtener los índices de la celda
      const { rowIndex, colIndex } = this.getCellIndices(col);
    
      // Re-renderizar el componente
      this.renderDynamicComponents(rowIndex, colIndex);
    
      // Actualizar el JSON y el editor
      this.updateJsonAndEditor();
    }

    applyKPI() {
      if (this.editingCell.content) {
        let kpiNumbers = Array.isArray(this.editingCell.content)
          ? this.editingCell.content
          : [this.editingCell.content];
    
        // Convertir a números de KPI
        kpiNumbers = kpiNumbers.map((item:any) => this.extractKpiNumber(item.toString()));
        this.editingCell.content = kpiNumbers;
    
        if (this.editingCell.multiSelection) {
          console.log("ApplyKPI Multiselección");
          // Filtrar las opciones seleccionadas para contentOptions
          this.editingCell.contentOptions = this.componentOptions.filter(option => this.editingCell.content.includes(option.value));
          console.log("🔰 ~ applyKPI ~ this.editingCell.contentOptions:", this.editingCell.contentOptions)
          this.editingCell.selectedContent = kpiNumbers[0] || null;
          console.log("🔰 ~ applyKPI ~ this.editingCell.selectedContent :", this.editingCell.selectedContent )
        } else {
          this.editingCell.selectedContent = kpiNumbers[0] || null;
        }
      }
    
      // Procesar alternativeContent de manera similar
      if (this.editingCell.showSwitch && this.editingCell.alternativeContent) {
        let altKpiNumbers = Array.isArray(this.editingCell.alternativeContent)
          ? this.editingCell.alternativeContent
          : [this.editingCell.alternativeContent];
    
        // Convertir a números de KPI        
        altKpiNumbers = altKpiNumbers.map((item: any) => this.extractKpiNumber(item.toString()));

        this.editingCell.alternativeContent = altKpiNumbers;
    
        if (this.editingCell.multiSelection) {
          // Filtrar las opciones seleccionadas para alternativeContentOptions
          this.editingCell.alternativeContentOptions = this.componentOptions.filter(option => this.editingCell.alternativeContent.includes(option.value));
          this.editingCell.alternativeSelectedContent = altKpiNumbers[0] || null;
        } else {
          this.editingCell.alternativeSelectedContent = altKpiNumbers[0] || null;
        }
      }
    
      this.editingCell.isComponent = true;
    
      // Actualizar gridData y gridDataString
      const { rowIndex, colIndex } = this.getEditingCellIndices();
      let gridDataStringObj = JSON.parse(this.gridDataString);
    
      gridDataStringObj[rowIndex].columns[colIndex] = {
        ...gridDataStringObj[rowIndex].columns[colIndex],
        ...this.editingCell,
      };
    
      this.gridDataString = JSON.stringify(gridDataStringObj, null, 2);
      this.gridData[rowIndex].columns[colIndex] = gridDataStringObj[rowIndex].columns[colIndex];
    
      this.updateEditorValue();
      this.renderDynamicComponents(rowIndex, colIndex);
    
      this.editingCell = null;
    }

    getEditingCellIndices() {
      let rowIndex = -1;
      let colIndex = -1;
    
      for (let i = 0; i < this.gridData.length; i++) {
        const columns = this.gridData[i].columns;
        for (let j = 0; j < columns.length; j++) {
          if (columns[j] === this.editingCell) {
            rowIndex = i;
            colIndex = j;
            return { rowIndex, colIndex };
          }
        }
      }
    
      return { rowIndex, colIndex };
    }
 
    
    private extractKpiNumber(value: string | number): number {
      if (typeof value === 'number') {
        return value;
      }
      
      const match = value.match(/\d+/);
      return match ? parseInt(match[0], 10) : 0;
    }


}