Lit-element: Use of this.function_name.bind(this) in LitElement

Created on 27 Sep 2018  路  10Comments  路  Source: Polymer/lit-element

Most helpful comment

A new PR in lit-html may just make this whole conversation a non-starter: https://github.com/Polymer/lit-html/pull/523

All 10 comments

I read we need to bind the Method to the Class or Object in order to have consitent/assured access to ths.

In regular ES6 classes do I need to bind in order to access this? In classes extended from LitElement do I need to bind in order to access this?

Without going into all about how this works in javascript:
Normally you want to bind when the function may be called by some other object, e.g. event handlers. If an object method calls another method in the same object, there is no disambiguation on what this is.

https://github.com/unrealprogrammer/lit-element-essentials/blob/1161412b5aff39ceeac48765fa53feafff6f1958/1-introduction-create-a-component/script.js#L17

Function IS bound, it's being called inside an arrow function which is lexically scoped, this is the same as:

setInterval(function() {
  this.updateTime();
}.bind(this), 1000);

Or alternatively in this case:

setInterval(this.updateTime.bind(this), 1000);

https://github.com/LostInBrittany/granite-inspector/blob/master/tree-view/granite-inspector-tree-view.js#L84

No clear example shown.


https://github.com/shprink/web-components-todo/blob/e340eea3b1657191804cf34c112c908bdd302c85/lit-element/src/my-todo.js#L11

Item is bound in the constructor to enable calling without binding later from other events, as well as being able to remove the events later on.

HOWEVER, in this case it doesn't seem that the bindings are necessary, as the methods are being used inside lexically scoped arrow functions inside of the same scope.


https://github.com/Westbrook/lit-element-todo/blob/ff23dac8ae25caa623840d2e3607975baa312d36/src/ToDo.js#L25

Mostly same as previous item, though since the items aren't inside arrow functions but instead being passed directly, the bindings ensure they have the proper scope.

So the rule is, if the Class Method is called from the outside AND the Method uses .this, it needs to be explicily bound to the class using .bind(this)? And this is because the value of (or context) .this changes based on the calling function?

@aadamsx Yes. If the calling context of a method may not be the host element, you will want to bind that method. You can take a look at bind-decorator or autobind-decorator. I tried bind-decorator but I couldn't compile my project, probably due to tsconfig mismatch. So, I ended up writing my own decorator based on bind-decorator. Below is an example.

@customElement('sample-element' as any)
export class SampleElement extends LitElement {
    private _handle = 0;

    constructor() {
        super();
        this.addEventListener('blur', this._handleBlur);
        this.addEventListener('focus', this._handleFocus);
    }

    @property({ type: Number })
    value = 0;

    connectedCallback(): void {
        super.connectedCallback();
        this._handle = setInterval(this._inc, 1000);
    }

    disconnectedCallback(): void {
        clearInterval(this._handle);
    }

    /** No binding required. Calling context is the host element. */
    private _handleFocus(): void {
        this.classList.add('focused');
    }

    /** No binding required. Calling context is the host element. */
    private _handleBlur(): void {
        this.classList.remove('focused');
    }

    /** Binding required. Calling context is the button. */
    @bind
    private _reset(): void {
        this.value = 0;
    }

    /** Binding required. Calling context is `window`. */
    @bind
    private _inc(): void {
        this.value++;
    }

    protected render(): TemplateResult {
        return html`
            <style>
                :host {
                    display: block;
                }
                :host(.focused) {
                    color: blue;
                }
            </style>
            ${this.value}
            <button @click=${this._reset}>Reset</button>
        `;
    }
}

declare global {
    interface HTMLElementTagNameMap {
        'sample-element': SampleElement;
    }
}

@aadamsx Actually, the binding issue comes from React.

There is a detailed explanation why it is so important in React. TL;DR: sending arrow function to a React Component causes unnecessary re-rendering and using class property without binding loses this context.

However, React and lit-html have quite different approaches for this thing. React uses approach with following features which are the source of a problem:

  • Direct injection of a handler function into a props object.
  • Shallow comparison of a previous and next props object during deciding if it is necessary to render.

These two features leads to the situation when previous handler function is not equal to the next one which means they are different, and shallow comparison will return false any time it is called.

However, lit-html uses completely different approach basing on the events. Any function goes to a @-marked attribute is just a source for addEventListener. Futhermore, to avoid adding event listeners continuously this process is higly optimized.

Thus, using inline arrow functions for event listeners doesn't cost you more than using them in array methods like map, filter or reduce. So I believe that inlining functions is the most preferrable and convenient way to work with event listeners in lit-html and lit-element.

You still can use a React way though, e.g. if you have a very big handler and you'd prefer to make code more readable. But examples you've provided that uses bind(this) just follows React way without considering the difference.

Maybe, it would be good to highlight this issue in documentation somewhere? It would be helpful for both newcomers from React and users who are not familiar with this issue at all.

@Lodin, @jolleekin, thanks for the feedback.

Yeah, coming from React, I'm a little confused.

A new PR in lit-html may just make this whole conversation a non-starter: https://github.com/Polymer/lit-html/pull/523

That's great! I hope this is fully documented in the API/Code guide.

Closing, as this has been addressed in lit-html, as noted above.

Was this page helpful?
0 / 5 - 0 ratings