es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Reemplace una cadena con un elemento de formulario reactivo.

Obtengo contenido de texto de una API de terceros; el contenido es creado usando un editor Wysiwyg por lo que puede tener cualquier número de etiquetas HTML dentro de él. Necesito reemplazar algunas cadenas de texto con listas de selección.

Por ejemplo, puedo tener una cadena de texto como esta:

<p>Hay [cuantos] listas desplegables en esta pregunta.</p>

Dentro de la aplicación angular, quiero que el resultado final sea:

<p> Hay 
  <select>
    <option value="1">Uno</option>
    <option value="2">Dos</option>
  </select> 
  listas desplegables en esta pregunta.
</p>

Tengo toda la lógica de reemplazo trabajando, lo que me resulta difícil es mantener el diseño del editor Wysiwyg y tener un formulario funcional.

Intenté hacerlo mediante el uso de un Pipe, lo cual funciona a nivel de presentación, pero no de manera funcional.

Mi idea era utilizar un Pipe en mi archivo component.ts:

multipleDropDownForm: FormGroup;
...
...
ngOnInit(): void {
  this.generateForm();
  this.formString = new DropdownsPipe().transform(this.formStringParts, this.replacementVariables, this.multipleDropDownForm);
}

generateForm(): void {
  const group: any = {};
  this.replacementVariables.forEach(item => {
    group[item.asString] = new FormControl(item.asString, Validators.required);
  });

  this.multipleDropDownForm = new FormGroup(group);
}

El código HTML:

<div class="question__lead-in" [innerhtml]="formString"></div>

El archivo dropdowns.pipe.ts:

export class DropdownsPipe implements PipeTransform {
  transform(value: string[], replacementVars: ReplacementVariable[], form: FormGroup): any {
    let compiledString = `<div [formgroup]="${form}">`;
    value.forEach(v => {
      if (replacementVars.find(r => r.withBrackets === v) === undefined) {
        compiledString += v;
      } else {
        const rv = replacementVars.find(rvf => rvf.withBrackets === v);
        let select = `<select name="${rv.asString}" formcontrolname="${rv.asString}">`;
        rv.answerOptions.forEach(ao => {
            select += `<option value="${ao.id}">${ao.text}</option>`;
        });
        select += '</select>';

        compiledString += select;
      }
    });
    compiledString += '</div>';
    return compiledString ;
  }
}

La presentación funciona perfectamente, pero funcionalmente no es así. Enviar el formulario simplemente muestra los controles predeterminados como valor:

La forma en que está hecho esto no permite que Angular registre adecuadamente los elementos select como controles de formulario.

Mi segundo intento fue usar componentes.

Archivos select-list.component.html:

<ng-container [formgroup]="form">
  <select name="{{ replacement.asString }}" formcontrolname="{{ replacement.asString }}">
    <option *ngfor="let option of replacement.answerOptions" value="{{ option.id }}">{{ option.text }}</option>
  </select>
</ng-container>

Archivo component.html:

<ng-container *ngfor="let s of formStringParts">
    <ng-container *ngif="isReplacementVariable(s); else textLabelBlock">
      <app-select-list [form]="multipleDropDownForm" [replacement]="getReplacementVariable(s)"></app-select-list>
    </ng-container>
    <ng-template #textlabelblock=""><span [innerhtml]="domSanitizer.bypassSecurityTrustHtml(s)"></span></ng-template>
</ng-container>

Funciona bien funcionalmente, pero falla en la presentación porque termino con algo como esto como HTML:

Y este es el resultado:

¿Hay alguna manera de reemplazar (n) textos con las respectivas listas de selección que me permita mantener el HTML de la API y trabajar con el envío del formulario?

Tags:  ,

Answer

  1. Avatar for davy.ai

    Una solución posible sería crear una directiva personalizada que reemplace la cadena con una lista desplegable y configure el control de formulario. De esta manera, se puede mantener el diseño del editor Wysiwyg y asegurarse de que Angular registre adecuadamente los elementos select como controles de formulario.

    Aquí hay un ejemplo de implementación:

    app.directive.ts

    import { Directive, ElementRef, Input, Renderer2 } from '@angular/core';
    import { FormGroup, FormControl, Validators } from '@angular/forms';
    
    @Directive({
      selector: '[appReplaceWithSelect]'
    })
    export class ReplaceWithSelectDirective {
      @Input() replaceString: string;
      @Input() answerOptions: { id: number, text: string }[];
    
      constructor(private el: ElementRef, private renderer: Renderer2, private formGroup: FormGroup) {
        const select = this.renderer.createElement('select');
        const formControlName = this.replaceString.slice(1, -1);
        this.renderer.addClass(select, 'form-control');
        this.renderer.setAttribute(select, 'formControlName', formControlName);
        const optionValues = this.answerOptions.map(option => option.id);
        optionValues.forEach(value => {
          const option = this.renderer.createElement('option');
          const text = this.renderer.createText(value.toString());
          this.renderer.appendChild(option, text);
          this.renderer.setAttribute(option, 'value', value.toString());
          this.renderer.appendChild(select, option);
        });
        this.renderer.insertBefore(this.el.nativeElement.parentNode, select, this.el.nativeElement);
        this.formGroup.addControl(formControlName, new FormControl('', Validators.required));
      }
    }
    

    En el código anterior, definimos una ReplaceWithSelectDirective que recibe la cadena de reemplazo y las opciones de respuesta como entradas. En el constructor, creamos un nuevo elemento select, agregamos opciones en función de las opciones de respuesta e insertamos la lista desplegable antes del elemento que contiene la cadena de reemplazo. Finalmente, agregamos un nuevo control de formulario al FormGroup padre para este elemento select.

    Para utilizar esta directiva en el componente, debe modificar su plantilla de la siguiente manera:

    component.html

    <ng-container *ngfor="let s of formStringParts">
      <ng-container *ngif="isReplacementVariable(s); else textLabelBlock">
        <span appreplacewithselect="" [replacestring]="s" [answeroptions]="getReplacementVariable(s).answerOptions" [formgroup]="multipleDropDownForm"></span>
      </ng-container>
      <ng-template #textlabelblock=""><span [innerhtml]="domSanitizer.bypassSecurityTrustHtml(s)"></span></ng-template>
    </ng-container>
    

    En esta plantilla actualizada, utilizamos la directiva appReplaceWithSelect en el elemento span que contiene la cadena de reemplazo. Pasamos la cadena de reemplazo y las opciones de respuesta como entradas, junto con el FormGroup multipleDropDownForm para enlazar el control del formulario. Cuando se aplica esta directiva al elemento, reemplazará la cadena de reemplazo con una lista desplegable y configurará el control del formulario según lo descrito anteriormente.

    Esta solución garantiza que los elementos select se registren adecuadamente en Angular como controles de formulario y mantengan el diseño del contenido del editor Wysiwyg.

Comments are closed.