Search code examples
javascriptcustom-element

Reference to self (this) inside CustomElement


I'm using this polyfill to implement a custom element in JavaScript. The self variable however references to Window inside my methods unless I call const self = this first.

Can anybody kindly explain why that is to me and perhaps suggest a better way to access the custom element instance within methods?

class DocumentPreview extends HTMLElement {
  constructor(self, documents) {
    self = super(self);
    self.documents = documents || [];
    self.innerHTML = Handlebars.templates['document_preview']();
  }

  connectedCallback() {
    // if I don't do this first ...
    const self = this;   // <<----------------------------------
    console.log("connected now");
    document.addEventListener('mqttsub', function(event) {
      // ... onMessage is undefined here:
      self.onMessage(event.detail);
    });
  }

  disconnectedCallback() {
    console.log("disconnected");
  }

  onMessage(message) {
    // Same story ...
    const self = this;   // <<----------------------------------
    Promise.resolve(true)
    .then(json("/documents/"))
    .then(ds => ds
      .filter(x => x.name==message.payload)
      .reduce((x, y) => y, undefined)
    )
    .then(d => json(sprintf("/document/%d/", d.id))())
    // ... here:
    .then(d => self.renderDocuments(d))
    .catch(console.log);
  }

  renderDocuments(d) {
    console.log('render', d);
  }
}

Solution

  • Try binding the methods onMessage() and renderDocuments() in the constructor using bind() as in this.methodName = this.methodName.bind(this). With this you'll be able to access properties and methods via this.

    class DocumentPreview extends HTMLElement {
      constructor(documents) {
        super();
    
        this.documents = documents || [];
        this.innerHTML = Handlebars.templates['document_preview']();
    
        this.onMessage = this.onMessage.bind(this);
        this.renderDocuments = this.renderDocuments.bind(this);
      }
    
      connectedCallback() {
        document.addEventListener('mqttsub', this.onMessage);
      }
    
      disconnectedCallback() {
        console.log("disconnected");
      }
    
      onMessage(event) {
        const { detail: message } = event;
    
        Promise.resolve(true)
          .then(json("/documents/"))
          .then(ds => ds
            .filter(x => x.name==message.payload)
            .reduce((x, y) => y, undefined)
          )
          .then(d => json(sprintf("/document/%d/", d.id))())
          // ... here:
          .then(d => this.renderDocuments(d))
          .catch(console.log);
      }
    
      renderDocuments(d) {
        console.log('render', d);
      }
    }
    

    Hopefully that helps!