How do you decouple Web Components?
Asked Answered
T

2

7

I'm trying to work frameworkless, with pure javascript Web Components. I want my Web Components to be able to work stand-alone and be used on different sites, and yet I also want two components to be able to communicate. So they should be able to communicate without being tightly coupled.

Back when I did Angular, this was easy. I can pass objects to a component through a HTML attribute, and the component receives it as an object rather than a string. But in pure javascript, attributes are always strings. What is the right way to pass objects around, or otherwise make Web Components aware of each other and able to communicate?

Theirs answered 27/11, 2017 at 12:24 Comment(2)
Sounds like you'll end up creating your own framework.Middleweight
That's what I'm hoping to avoid. I want to stick as closely to pure javascript with as little assumptions as possible about the environment or the existence of other objects, and yet have an easy way to bring two objects in contact with each other.Theirs
M
4

Here is a sample app with two native V1 Web Components. <component-1> can talk to <component-2> because you supply an ID into <component-1> and that ID refers to the ID set on <component-2>.

This is similar to how the <label> tag work with its for attribute.

HTML

<component-1 link-id="c2"></component-1>
<hr/>
<component-2 id="c2"></component-2>

JS

// Class for `<component-1>`
class Component1 extends HTMLElement {
  constructor() {
    super();
    this._linkedComponent = null;
    this._input = document.createElement('input');
    this._input.addEventListener('focus', this._focusHandler.bind(this));

    this._button = document.createElement('button');
    this._button.textContent = 'Add';
    this._button.addEventListener('click', this._clickHandler.bind(this));
  }

  connectedCallback() {
    this.appendChild(this._input);
    this.appendChild(this._button);
  }

  static get observedAttributes() {
    return ['link-id'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      if (newVal === null) {
        this._linkedComponent = null;
      }
      else {
        this._linkedComponent = document.getElementById(newVal);
      }
    }
  }

  _clickHandler() {
    if (this._linkedComponent) {
      this._linkedComponent.value = this._input.value;
    }
  }

  _focusHandler() {
    this._input.value = '';
  }
}

// Class for `<component-2>`
class Component2 extends HTMLElement {
  constructor() {
    super();
    this._textArea = document.createElement('textarea');
    this._textArea.setAttribute('style','width:100%;height:200px;');
  }

  connectedCallback() {
    this.appendChild(this._textArea);
  }

  set value(newValue) {
    this._textArea.value += (newValue+'\n');
  }
}

customElements.define('component-1', Component1);
customElements.define('component-2', Component2);

<component-1> will only talk to <component-2> if there is a component with the ID that was provided to <component-1> through its link-id attribute.

Mayman answered 27/11, 2017 at 23:14 Comment(2)
That's a lot of boilerplate. Quite a change when you come from Angular, but I guess it's unavoidable when you try to work in pure js.Theirs
It is a bit of boilerplate, but you are creating a true HTML Element just like HTMLButtonElement or HTMLDivElement. There are ways to reduce your code and libraries that can also reduce the code. But I use a snippet in my editor that auto generates the default code and saves me all the typing. Personally I don't mind the boilerplate.Mayman
M
6

With Web Components you can pass objects through attributes as you said, but you can also pass an object with a method, or throug a property (which is actually a setter method).

<my-component id="comp1"></my-component>
...
var myObject = { y:1, y:2 }
comp1.value = myObject     //via property
comp1.setValue( myObject ) //via method
Mcdermott answered 27/11, 2017 at 13:26 Comment(0)
M
4

Here is a sample app with two native V1 Web Components. <component-1> can talk to <component-2> because you supply an ID into <component-1> and that ID refers to the ID set on <component-2>.

This is similar to how the <label> tag work with its for attribute.

HTML

<component-1 link-id="c2"></component-1>
<hr/>
<component-2 id="c2"></component-2>

JS

// Class for `<component-1>`
class Component1 extends HTMLElement {
  constructor() {
    super();
    this._linkedComponent = null;
    this._input = document.createElement('input');
    this._input.addEventListener('focus', this._focusHandler.bind(this));

    this._button = document.createElement('button');
    this._button.textContent = 'Add';
    this._button.addEventListener('click', this._clickHandler.bind(this));
  }

  connectedCallback() {
    this.appendChild(this._input);
    this.appendChild(this._button);
  }

  static get observedAttributes() {
    return ['link-id'];
  }

  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal !== newVal) {
      if (newVal === null) {
        this._linkedComponent = null;
      }
      else {
        this._linkedComponent = document.getElementById(newVal);
      }
    }
  }

  _clickHandler() {
    if (this._linkedComponent) {
      this._linkedComponent.value = this._input.value;
    }
  }

  _focusHandler() {
    this._input.value = '';
  }
}

// Class for `<component-2>`
class Component2 extends HTMLElement {
  constructor() {
    super();
    this._textArea = document.createElement('textarea');
    this._textArea.setAttribute('style','width:100%;height:200px;');
  }

  connectedCallback() {
    this.appendChild(this._textArea);
  }

  set value(newValue) {
    this._textArea.value += (newValue+'\n');
  }
}

customElements.define('component-1', Component1);
customElements.define('component-2', Component2);

<component-1> will only talk to <component-2> if there is a component with the ID that was provided to <component-1> through its link-id attribute.

Mayman answered 27/11, 2017 at 23:14 Comment(2)
That's a lot of boilerplate. Quite a change when you come from Angular, but I guess it's unavoidable when you try to work in pure js.Theirs
It is a bit of boilerplate, but you are creating a true HTML Element just like HTMLButtonElement or HTMLDivElement. There are ways to reduce your code and libraries that can also reduce the code. But I use a snippet in my editor that auto generates the default code and saves me all the typing. Personally I don't mind the boilerplate.Mayman

© 2022 - 2024 — McMap. All rights reserved.