Skip to content
This repository was archived by the owner on Jan 25, 2022. It is now read-only.

Dynamic private field access without decorators? #236

Closed
xlaywan opened this issue Apr 28, 2019 · 8 comments
Closed

Dynamic private field access without decorators? #236

xlaywan opened this issue Apr 28, 2019 · 8 comments

Comments

@xlaywan
Copy link

xlaywan commented Apr 28, 2019

At the moment, private fields are supported in Chrome 74, but how I can use them in custom elements with declarative html templates?

For example I have custom element with declarative HTML template:

<div>Hello, {{planet}}!</div>
<div>Hey, {{#person}}!</div>

and JS class:

class MyEl extends HTMLElement {
  planet = 'Pluto'
  #person = 'John'

  render() {
    let props = this.template.getBindings() // ['planet', '#person']
    let state = {}
    props.forEach(prop => {
      if (prop.startsWith('#')) {
        // here we can access private property by this.#planet
        // but we can't do it dynamically like this.['#planet'] or Reflect.get(...)
        state[prop] = ???
      } else {
        state[prop] = this[prop]
      }
    }
    this.template.update(state)
  }
}

Here #204 is probably a solution with decorators. But decorators are far from shipping and it brings a boilerplate code, because we need to decorate every private field.

Related to HTML Template Instantiation

@ljharb
Copy link
Member

ljharb commented Apr 28, 2019

What would be the value of a field that a user-supplied template could access being private?

@bakkot
Copy link
Contributor

bakkot commented Apr 28, 2019

You can put something which supports dynamic access in a private field:

class MyEl extends HTMLElement {
  planet = 'Pluto'
  #_ = { person = 'John' }

  render() {
    let props = this.template.getBindings() // ['planet', '#person']
    let state = {}
    props.forEach(prop => {
      if (prop.startsWith('#')) {
        state[prop] = this.#_[prop]
      } else {
        state[prop] = this[prop]
      }
    }
    this.template.update(state)
  }
}

@xlaywan
Copy link
Author

xlaywan commented Apr 28, 2019

@bakkot It is not convenient. I can write same code w/o private fields, just using Symbol

let privateSymbol = Symbol()

class MyEl extends HTMLElement {
  planet = 'Pluto'
  [privateSymbol] = { person = 'John' }

  render() {
    let props = this.template.getBindings() // ['planet', '#person']
    let state = {}
    props.forEach(prop => {
      if (prop.startsWith('#')) {
        state[prop] = this[privateSymbol][prop]
      } else {
        state[prop] = this[prop]
      }
    }
    this.template.update(state)
  }
}

This approach also works for any JS class, not only for custom elements. But the private fields were introduced to simplify such code, weren't they?

@xlaywan
Copy link
Author

xlaywan commented Apr 28, 2019

A more realistic example:

<template>
  <div is-drag-over="{{#isDragOver}}" on-mouseover="{{#checkIfDroppable}}" on-mouseup="{{#dropHandler}}">
    <mdi-icon icon="folder"></mdi-icon>
    <div contenteditable="{{#isRenameMode}}" on-input="{{#renameInputHandler}}">{{folder.name}}</div>
    <mdi-icon icon="pencil" on-click="{{#renameFolder}}"></mdi-icon>
    <mdi-icon icon="delete" on-click="{{#deleteFolder}}"></mdi-icon>
  </div>
</template>

<script>
  class FolderElement extends HTMLElement {
    folder
    compact = true

    #isDragOver = false
    #isRenameMode = false

    #renameFolder() {
      this.#isRenameMode = true
    }

    #renameInputHandler(e) {
      // ...
    }

    #checkIfDroppable(e) {
      // ...
    }

    #dropHandler(e) {
      // ...
    }

    #deleteFolder() {
      // ...
    }

    render() {
      let props = this.template.getBindings()
      let state = {}
      props.forEach(prop => {
        if (prop.startsWith('#')) {
          state[prop] = ???
        } else {
          state[prop] = this[prop]
        }
      }
      this.template.update(state)
    }
  }
</script>

@kaizhu256
Copy link

from a realistic maintennance-perspective, i don't understand why class-methods in example above need to be private.

is it so you can overload these methods in subclasses? overloading is a flawed design-pattern that makes code debugging/refactoring a nightmare as you try to figure-out which function does what when grepping/searching-and-replacing through source-code.

is it because they are volatile internal-api's that will frequently change, so you don't want users depending on them? realistically, all frontend-code is volatile and subject to frequent armageddon-like rewrites. humans are terrible at predicting-in-advance what the ux of a product should be and sticking with it.

is it for ... security reasons? >facepalm<

@littledan
Copy link
Member

Some frameworks are looking at custom Babel transforms to generate this kind of introspection capability. If this catches on, I can imagine making a follow-up proposal for syntax for it. However, currently, you can use a workaround as explained higher on this thread.

@Jamesernator
Copy link

I will also point out that referencing a given private field might correspond to multiple fields on an object due to subclassing, the names are purely lexical. I suppose you could have a lexical form like class.getPrivate(stringName) that's scoped lexically like private fields. But something I will point out is that in their current form private fields are safely minify-able which any such proposal would necessarily break (at least in classes that use the feature).

I think it'd just be better to wait for decorators as I feel such a proposal would at least happen no sooner than decorators anyway, and while you'd probably need to decorate each private field this would at least mean you only pay the introspection costs for fields that are actually needed by the template.

@littledan
Copy link
Member

I think this question is adequately answered in several of the above comments. Thanks for the discussion here.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants