Alpine: Using ES6 classes to initialize a component scope (x-data)

Created on 12 Mar 2020  路  27Comments  路  Source: alpinejs/alpine

Hello, everyone!
I couldn't find any documentation on this specific use case and was wondering if there are any caveats to this approach. Testing a simple case on Codepen seems to work fine.

<div x-data="App.Greeter">
  <span x-text="`Hello ${name}`"></span>
  <br />
  <input type="text" x-model="name">  
</div>
class Greeter { 
  constructor() {
    this.name = "James"
  }
}

window.App = { 
    Greeter: new Greeter() 
}

Most helpful comment

Then it's settled! Thank you guys for all the effort so far 馃槃.

All 27 comments

There shouldn't be too many caveats, the usual this binding issues could occur.

Off the top of my head I don't think there'll be too many issues (if any), instances of a class (new MyName()) in JavaScript have very few differences to regular objects defined with object literal syntax const obj = {} especially the way they're used in Alpine.js (class is syntactic sugar over the prototype + constructor functions).

Hey @HugoDF, what kind of this binding issues? (If you don't mind me asking)

The classic:

```
class Hello {
hey() {
return this.prop
}
}

const hello = new Hello()
const { hey } = hello
hey() // error
``

@HugoDF How would that be a problem using it with Alpine though? I mean, once I have the object instance passed to x-data.

Like I said I can't think of any, Alpine.js is pretty good with binding this

@calebporzio Could you weigh in on this?

Put it another way, if Alpine manages to keep this accurate with object literals, I don't see any case where it would suddenly break for classes.

I think it's a case of "go for it and let us know if you find any issues"

@HugoDF Ok then, I'll do it.
My primary concern was going off rails with the Alpine approach and having no support whatsoever. Thank you very much for your time, I'm closing this issue for now.

Hi, again @HugoDF.

I'm reopening this issue because I spotted a problem using the latest 2.1.2 version.
My previous example only works for version 2.0.0 and since I'm using promises, I had to update because of this: Proxies break Promises.
Right now, I can't get any reactivity on my component, any suggestions?

We switched out the reactivity engine in v2.1 you're probably going to need to switch to objects

I'm doing this right now, thanks for confirming.
So, I guess we are back to square one regarding using ES6 classes.
Anything that can be done to start this conversation or this is now just off the table?

I think it wasn't explicitly supported but if the reactivity engine we now use doesn't support it, I don't think this feature is worth going back to a hand-rolled proxy reactivity layer.

Although I think there might be some other discussions around that (the proxy/reactivity layer) since observable-membrane is causing issues on IE11.

I see...
Well, if you don't mind I'm gonna leave this open for future investigation.
Also, I think it's worth checking with the people involved with observable-membrane if there are any plans to implement this feature natively.

@thiagomajesk The issue seems to be here -> https://github.com/salesforce/observable-membrane/blob/a3215d6d4e1c7c39f4504a6a0234f3121926a80f/src/reactive-membrane.ts#L68

That line makes property reactive only if:

  • their prototype is an Object
  • their prototype is null
  • the prototype of their prototype is null
    (Javascript inheritance)

When using a ES6 class, its prototype is custom and different from Object.
In your case the prototype of the prototype is Object but if the class extended another class, the membrane would have to call getPrototype recursively up to the root.

There could be a reason why Salesforce don't support it but it's more a question for them (I've no clue, sorry).

Well, at least there's an official response: https://github.com/salesforce/observable-membrane/issues/42#issuecomment-599444133.
The only workaround I can think of would be doing what Knockout does: Initialize the properties you want to be reactive instead of the whole object:

class Greeter {
  constructor() {
    this.name = observable("James")
  }
}

I see. We had the same issue with the old proxy implementation, some objects such as Date, RegExp, etc didn't work before of the this binding.

@SimoTod At least that comment sparked the conversation on the observable-membrane side.
I think we should wait to see the options we have.

Yeah, the main problem is that salesforce is corporate so all changes will probably follow slow processes and developers need to be allocated to work on it. They can't really change it without asking the business if they use the membrane on one of their products, it's understandable.
Let's see what happens anyway.
Unfortunately, I think you should not use classes or try some workaround for now since it doesn't seem like something we can fix in Alpine.

Hi guys, closing this issue due to inactivity. If you think this feature is a necessity, please open another issue for discussion.

Hi @ryangjchandler!
This is being looked at by @SimoTod already here: https://github.com/salesforce/observable-membrane/issues/42. Please, keep it open so we can remember to track progress in this project too.

No worries, the comment on the other side hasn't had much activity yet. I think we should keep this issue closed for now until we see some progress on Salesforce's end, what do you think? Happy to open the issue up again if you think it's beneficial!

I've chatted to the salesforce guys and they don't have any plans to support this use case due to other complexities.
Unless we rollout our own membrane (but i think we prefer not to), I would say that it's not supported unfortunately.

Yeah sounds about right, got that impression from the comments on the issue that side. I'll keep this issue closed then. It's possible we might be able to add support in the future, but there's no promises.

Then it's settled! Thank you guys for all the effort so far 馃槃.

Then it's settled! Thank you guys for all the effort so far 馃槃.

No need to thank me, thank @SimoTod! I'm just triaging the issues haha

Hi, I try to achieve something like this (use class in component).
Unfortunately second component is not reactive.

I've tried both build versions without success.

<div x-data="component1()">
  <h1>Component #1</h1>
  <input type="text" x-model="user.name">
  <div x-text="user.name"></div>
</div>

<div x-data="component2()">
  <h1>Component #2</h1>
  <input type="text" x-model="user.name">
  <div x-text="user.name"></div>
</div>
const component1 = () => {
  return {
    user: {
      name: 'UserName'
    }
  }
}

class User {
  constructor() {
    this.name = 'UserName'; 
  }
}
const component2 = () => {
  return {
    user: new User()
  }
}

Above example in codepen.

Hi @wgasowski
ES6 classes are not supported by the current reactivity layer only literal objects as per documentation.
You can find more details in the conversation above.

Was this page helpful?
0 / 5 - 0 ratings