Search code examples
javascripthtmlweb-component

Access to html tag from inside Web Components


I'm learning the firsts concepts of js web components. I pretty nob in these and try to make a simple example. My component is just a button that pretends to change color to a div.

My example work I expected but I noticed that my approach is not too much "component way", if the element that I attempt to change is in my web component to instead outside of it.

This is my html file:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <div class="main">
      <h1>Yeah Web Components!</h1>
      <my-component></my-component>
    </div>

    <script src="myComponent.js"></script>
  </body>
</html>

This is my component .js:

const template = `
  <style>
    .container{
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    .box{
        width: 100px;
        height: 100px;
        background: red;
    }
    .hi-button{
      margin-top: 10px;
    }
    
  </style>
  <div class="container">
    <div class="box"></div>
    <button class="hi-button">Change</button>
  </div>
 
`;

class MyFirstTest extends HTMLElement{
  constructor(){
    super()
    const shadowRoot = this.attachShadow({ mode: 'open' }); 
    shadowRoot.innerHTML = template; 
  }

  changeButtonColor(){
      const box = this.shadowRoot.querySelector(".box");
      if(box.style.background === 'red'){
        box.style.background = 'blue';
      }else{
        box.style.background = 'red';
      }
  }

  connectedCallback(){
      const event = this.shadowRoot.querySelector(".hi-button");
      event.addEventListener('click', () => this.changeButtonColor());
  }

  disabledCallback(){
    const event = this.shadowRoot.querySelector(".button-test");
    event.removeEventListener();
  }

}

customElements.define('my-component', MyFirstTest);

As I said the functionality works fine but I don't want my div to be in my web component but in my html file and mi component be only the button.

For example, my html file would be something like this:

.......

  <link rel="stylesheet" href="index.css" />
  </head>
  <body>
    <div class="main">
      <h1>Yeah Web Components!</h1>
      <div class="my-div-to-change"></div>
      <my-component></my-component>
    </div>
  </body>

.......

Is possible for web components to work in this way?


Solution

  • The best way to maintain the independence between components is use events.

    Web components dispatch an event that is listened by the parent container. And then, parent do the necessary action.

    If you want to talk "directly" between component and parent, the system is pretty coupled and is not a good way. Of course you can, but is not recommended.

    You can check this answer which is clearer.

    Also, answering your question using events. Is as simple as this:

    First, into your component you have to dispatch the event in this way:

    this.dispatchEvent(new CustomEvent("button-clicked", { 
        bubbles: true,
    }));
    

    You can check here the use of this.

    Also, the parent will listen using:

    document.addEventListener("button-clicked", changeColor);
    

    So, when the button is clicked, the parent will trigger the function changeColor with the logic inside you want.

    In this way you are using a Web Component to do actions into parent container but the system is not coupled. Parent can work without child and child can work without parent. Both can be used separately. Of course, the event will not be dispatch or listen but there is no a dependency between components.

    Also this is an example how it works.

    const template = `
    
      <div class="container">
    
        <button class="hi-button">Change</button>
      </div>
     
    `;
    
    class MyFirstTest extends HTMLElement{
      constructor(){
        super()
        const shadowRoot = this.attachShadow({ mode: 'open' }); 
        shadowRoot.innerHTML = template; 
      }
    
      changeButtonColor(){
          //Call parent
          this.dispatchEvent(new CustomEvent("button-clicked", { 
                bubbles: true,
            }));
      }
    
      connectedCallback(){
          const event = this.shadowRoot.querySelector(".hi-button");
          event.addEventListener('click', () => this.changeButtonColor());
      }
    
      disabledCallback(){
        const event = this.shadowRoot.querySelector(".button-test");
        event.removeEventListener();
      }
    
    }
    
    customElements.define('my-component', MyFirstTest);
    <!DOCTYPE html>
    <html lang="en">
      <head>
       <style>
        .container{
          display: flex;
          flex-direction: column;
          align-items: center;
        }
        .box{
            width: 100px;
            height: 100px;
            background: red;
        }
        .hi-button{
          margin-top: 10px;
        }
        
      </style>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="index.css" />
      </head>
      <body>
        <div class="main">
          <h1>Yeah Web Components!</h1>
          <div class="box"></div>
          <my-component></my-component>
        </div>
    
        <script src="myComponent.js"></script>
        <script>
        //Add listener to do the action
        document.addEventListener("button-clicked", changeColor);
        function changeColor(){
          var box = document.querySelector(".box");
          if(box.style.backgroundColor === 'red'){
            box.style.backgroundColor = 'blue';
          }else{
            box.style.backgroundColor = 'red';
          }
        }
        </script>
      </body>
    </html>