LWC: Difference between "window" and "this"

  1. this refers to the default class and not window.

  2. Container component (not owner) can add event listeners and access elements directly on this and not template. So, we should use this.addEventListener and this.querySelector

  3. Owner component has to add event-listeners and identification through template. So, we should use this.template.addEventListener and this.template.querySelector

Please find below the example:

Consider 3 components Grandparent, parent, and child as below:

Grandparent.html:

<template>
    <div>Grandparent:</div>
    <c-parent>
        <span slot='myslot'>
            <c-child></c-child>
        </span>
    </c-parent>
</template>

Here, grandparent is the owner of c-child and parent component is only the container of child

Grandparent.js:

export default class Grandparent extends LightningElement {
    constructor() {
        super();
        console.log('this => ', this);
        this.addEventListener('myevent', this.myeventHandler);
        this.template.addEventListener('myevent', this.myeventHandlerTemplate);
    }
    renderedCallback() {
        console.log("Grandparent renderedCallback => ", this.querySelector('c-child'));
        console.log("Grandparent renderedCallback template => ", this.template.querySelector('c-child'));
    }
    myeventHandler(event) {
        console.log('Grand parent - myevent handled');
    }
    myeventHandlerTemplate(event) {
        console.log('Grand parent template - myevent handled');
    }
}

parent.html:

<template>
    <div>
        <slot name='myslot'></slot>
    </div>
</template>

parent.js:

export default class Parent extends LightningElement {
    constructor() {
        super();
        console.log('this => ', this);
        this.addEventListener('myevent', this.myeventHandler);
        this.template.addEventListener('myevent', this.myeventHandlerTemplate);
    }
    renderedCallback() {
        console.log("parent renderedCallback => ", this.querySelector('c-child'));
        console.log("parent renderedCallback template => ", this.template.querySelector('c-child'));
    }
    myeventHandler(event) {
        console.log('parent - myevent handled');
    }
    myeventHandlerTemplate(event) {
        console.log('parent template - myevent handled');
    }
}

We are adding event-listeners and query-selectors on both direct this and template to check the functionality in both parent and grandparent.

child.js:

export default class Child extends LightningElement {
    connectedCallback() {
        this.dispatchEvent(new CustomEvent('myevent', { bubbles: true }));
    }
}

bubbles:true and composed:false (default is false) will make the event bubble up until shadow boundary.


Below are the logs we get: (in order)

this =>  Grandparent {setAttribute: ƒ}
this =>  Parent {setAttribute: ƒ}
parent template - myevent handled
parent - myevent handled
Grand parent template - myevent handled
parent renderedCallback =>  c-child
parent renderedCallback template =>  null
Grandparent renderedCallback =>  null
Grandparent renderedCallback template =>  c-child
  1. As you see, this has returned class instance.

  2. querySelector for parent component worked only for direct this and returned null on template.

  3. Also for event listener, container component - parent listened on direct this. However, there appears to be some bug/unknown issue since container component is able to listen to the event on template.

  4. Only template binding worked in Grandparent.