Search code examples
javascriptweb-componentlit-element

Is it bad practice to create a web-component inside another and append it to said other?


Essentially I have a web component "x" and I dynamically create a form component inside the "x" which will be appended to "x".

I could just do it in the place I create "x", after creating "x", of course.

Basically this:

class X extends LitElement {

  render() {
    return html`
      <div>
        <slot name="form-component">${this.appendFormComponent()}</slot>
      </div>
      <slot></slot>
    `
  }

  appendFormComponent() {
    const formComponent = document.createElement('input')
    formComponent.slot = "form-component"
    this.append(formComponent)
  }

  // side note, is running this append inside the render function a terrible 
  // idea and where should I do it instead? I mean doing it in the render 
  // function does appear to work...
}

Solution

  • As you suspected, this is definitely a terrible idea because you are mixing imperative paradigm with declarative paradigm. However, if you really need to do this and since you are using LitElement, you can nicely abstract the declarative and imperative UI code using appropriate lifecycle methods:

    class X extends LitElement {
    
      render() {
        return html`
          <div>
            <slot name='form-component'></slot>
          </div>
          <slot></slot>
        `;
      }
    
      // Executed only once
      firstUpdated() {
        const formComponent = document.createElement('input');
    
        formComponent.slot = 'form-component';    
        this.append(formComponent);
      }
    }
    

    Also, the approach you are attempting is probably problematic. Your problem would be easily solved by render function only:

    class X extends LitElement {
    
      render() {
        return html`
          <div>
            <slot name='form-component'>
              <!-- Notice the use of INPUT TAG here -->
              <input type='text' />
            </slot>
          </div>
          <slot></slot>
        `;
      }
    }
    

    Using something like firstUpdated with document.createElement should be used to create UI components which have offset elements that break the UI as Function of State notion. Such components are date pickers, multi select dropdown, dialog boxes, etc. which directly append DOM elements to the body for managing Z-index and fixed positioning accurately.

    Further, as per your comments, if you have a dynamic function which needs to be assigned to the input text, simply create a wrapper function like:

    class X extends LitElement {
    
      // Input change event handler
      onChange() {
    
        // A guard to check presence of dynamic function
        if (this.someDynamicFuction) {
          this.someDynamicFuction();
        }
      }
    
      render() {
        return html`
          <div>
            <slot name='form-component'>
              <!-- Notice the use of INPUT TAG here -->
              <input type='text' @change=${this.onChange} />
            </slot>
          </div>
          <slot></slot>
        `;
      }
    }