Search code examples
javascriptweb-componentshadow-domcustom-element

add class dynamically in web components from JavaScript using classList


How to add dynamic styles to the shadow DOM

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Document</title>
    <link rel="import" href="nav-component.html">
</head>
<body>
    <app-nav></app-nav>
</body>
</html>

nav-component.html

<template>
  <style>
    .btn {
        background-color: green;
        padding: 10px 20px;
    }
  </style>
    <button onclick="getstyles()">ENTER</button>
</template>
<script src="nav-component.js"></script>

nav-component.js

let template = document.currentScript.ownerDocument.querySelector('template');
let clone = document.importNode(template.content, true);
let proto = Object.create(HTMLElement.prototype);

proto.createdCallback = function () {
    let root = this.createShadowRoot();
    root.appendChild(clone);
}
document.registerElement('app-nav', {
    prototype: proto
});

function getstyles() {
    console.log('it works');
    document.querySelector('button').classList.add('btn');
    // document.currentScript.ownerDocument.querySelector('button').classList.add('btn');
}

have to add btn class to button element , so that its styles added to button element

got error Uncaught TypeError: Cannot read property 'classList' of null

Demo


Solution

  • First of all document.registerElement is deprecated so I answered class based custom element solution here...

    The solution is to get the document from document.currentScript.ownerDocument

    class AppNavElement extends HTMLElement {
        constructor() {
            super()
    
            // import from ownerDocument
            const importDoc = document.currentScript.ownerDocument;
            let shadowRoot = this.attachShadow({mode: 'open'});
            const template = importDoc.querySelector('#template');
            const instance = template.content.cloneNode(true);
            shadowRoot.appendChild(instance);
    
            // Event Listener
            this.addEventListener('click', e => this.changeStyle());
        }
    
        changeStyle() {
            this.shadowRoot.querySelector('button').classList.add('btn')
        }
    }
    
    customElements.define('app-nav', AppNavElement)
    

    Update:

    To listen the button element only use the connectedCallback thanks @bhv

    connectedCallback() { 
        let btn = this.shadowRoot.querySelector('button') 
        // Event Listener 
        btn.addEventListener('click', e => this.changeStyle()); 
    }