title: Web Components: Is there an equivalent to attributeChangedCallback for properties?
body: <p>You're not supposed to put rich data (objects, arrays, functions) in HTML element attributes. Instead, it's suggested to only put rich data in properties (according to the <a href="https://developers.google.com/web/fundamentals/web-components/best-practices#aim-to-only-accept-rich-data-objects-arrays-as-properties" rel="nofollow noreferrer">Google custom elements best practices article</a>). I need to run actions when these properties are updated. We have <code>observedAttributes</code> and <code>attributeChangedCallback</code>, but there's nothing similar for properties.</p>
  
  <p>Let's say I have a <code>user</code> prop with things like <code>name</code>, <code>DoB</code>, and <code>address</code> on it. I thought I might be able to trick <code>observedAttributes</code> by putting a bunk setter <em>a la</em></p>
  
  <pre><code>set user(val) {
    return;
  }
  </code></pre>
  
  <p>Didn't work. <code>return this.user = val</code> gives an infinite loop.</p>
  
  <p>My only idea at this point is to have a property called <code>_user</code> that simply gets set to <code>[Object object]</code> on every change, which triggers the change I actually want. Don't really like that though.</p>
  
  <p><strong>UPDATE</strong>: This is what I'm currently doing</p>
  
  <p>In <code>user-info.js</code>:</p>
  
  <pre><code>class UserInfo extends HTMLElement {
    connectedCallback() {
      subscribers.push({ element: this, props: ['user'] });
      this._user = state.user;
      this.render();
    }
    static get observedAttributes() {
      return ['user'];
    }
    attributeChangedCallback(name, oldValue, newValue) {
      this.render();
    }
    get user() {
      return this._user;
    }
    set user(val) {
      if (JSON.stringify(val) !== JSON.stringify(this._user)) {
        this._user = val;
        return this.setAttribute('user', val);
      }
    }
    render() {
      this.innerHTML = `&lt;span&gt;${this._user.name}&lt;/span&gt; was born on &lt;span&gt;${this._user.dob}&lt;/span&gt;`;
    }
  }
  </code></pre>
  
  <p>In <code>main.js</code>:</p>
  
  <pre><code>document.querySelector('.actions--user').addEventListener('input', e =&gt; {
    state.user = {...state.user, [e.target.dataset.action]: e.target.value};
  })
  </code></pre>