Search code examples
recaptchaweb-componentshadow-domcustom-element

Custom Element Web Component Shadow DOM Vendor Scripts/Elements


When working with Custom Elements that leverage Shadow DOM, what is the official approach to injecting 3rd party scripts and elements such as Invisible reCAPTCHA which require scripts such:

<script src="https://www.google.com/recaptcha/api.js" async defer></script>` 

for HTML elements such as a <button> to be leaded and reCAPTCHA to be rendered?

shadowRoot doesn't seem to have anything like head, is the script supposed to be added to the created template's innerHTML? Or is a <script> to be appended to the shadowRoot via appendChild in connectedCallback()? What is the official approach to working with 3rd party libraries within Shadow DOM? Loading the script on the page containing the rendered Custom Element doesn't seem to trigger a render due to Shadow DOM.

const template = document.createElement('template');
template.innerHTML = `
    <form>
        <button class="g-recaptcha" 
            data-sitekey="your_site_key" 
            data-callback='onSubmit'>Submit</button>
    </form>
`;

class CustomElement extends HTMLElement {
  constructor() {
    super(); // always call super() first in the ctor.
    this.attachShadow({mode: 'open'});
    this.shadowRoot.appendChild(template.content.cloneNode(true));
  }
  connectedCallback() {
    ...
  }
  disconnectedCallback() {
    ...
  }
  attributeChangedCallback(attrName, oldVal, newVal) {
    ...
  }
}

Thank you for any guidance you can provide.


Solution

  • There's no offical approach because the solution depends on the 3rd party library implementation.

    However, in the case of reCaptcha, the workaround is to expose the <button> in the normal DOM, and inject it in the Shadow DOM via the <slot> element.

    class extends HTMLElement {
        constructor() {
            super()
            this.attachShadow( {mode: 'open'} ).innerHTML= `
                <form>
                    <slot></slot>
                </form>`                
        }
    
        connectedCallback() {
            this.innerHTML = `                  
                <button
                    class="g-recaptcha"
                    data-sitekey="your_site_key"
                    data-callback="onSubmit">Submit</button>`
            }
        })