angularselectionappendchildangular-renderer2

Wrap a character in a span element on key press with renderer2 Angular


I have a contenteditable div which fires an event when the user press '@'. what it does is wrap the pressed @ letter in a custom element in the same position.

Here is the code

    <div  contenteditable="true" role="textarea" (keydown.@)= "onWrap()">
    </div>
    onWrap() {
        setTimeout(() => {
          const caretPosition = window.getSelection().getRangeAt(0);
          const range = document.createRange();
          range.setStart(caretPosition.commonAncestorContainer, caretPosition.startOffset - 1);
          range.setEnd(caretPosition.commonAncestorContainer, caretPosition.endOffset );
          const previousNode = (range.cloneContents().lastChild as HTMLElement);
          const newWrap = this.renderer.createElement('my-custom-component');
          //range.surroundContents(newWrap );
        }, 10);
      }

so after @ press the editable text must become

    <div  contenteditable="true" role="textarea">
    this is my editable content and i want to insert here <my-custom-component>@</my-custom-component> and the rest of the text
    </div>

and i want the <my-custom-component> to be converted

  1. I'm using surroundContents for the moment but it's not recommanded and the custom component is not changing. Perhaps i must use renderer.appendChild but i dont know how.
  2. i'm using timeout because the @ is not detected on keypress and i want not to use it.

Solution

  • As mentioned in the comments you can take advantage of ComponentRendererFactory and create and attach a component dynamically. What you need is inject required services. Injector is required for resolving component factory and ApplicationRef is to attach the component into DOM

    constructor(
        private injector: Injector,
        private cfr: ComponentFactoryResolver,
        private applicationRef: ApplicationRef
      )
    

    Define a dummy component :

    @Component({
      selector: "app-wrapper",
      styleUrls: ["./wrapper.component.css"],
      template: `
        <span style="background-color:aqua">
          x:
          {{ innerText }}
        </span>
      `
    })
    export class WrapperComponent {
      innerText: string;
    }
    

    Create component dynamically

     const wrap = this.renderer.createElement("wrapper-container"); // this can be anything but it is required
        const factory = this.cfr.resolveComponentFactory<WrapperComponent>(WrapperComponent);
        const cmp = factory.create(this.injector, [], wrap); // attach your component into host element : `wrapper-container`
        this.applicationRef.attachView(cmp.hostView); // attach component into DOM   
        const contents = range.extractContents(); // extract contents
        cmp.instance.innerText = contents.textContent; // set components text   
        range.insertNode(wrap); // insert everything into range.
    

    Here is the Stackblitz sample.