New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Using ES6 classes to initialize a component scope (x-data) #270
Comments
There shouldn't be too many caveats, the usual Off the top of my head I don't think there'll be too many issues (if any), instances of a class ( |
Hey @HugoDF, what kind of |
The classic:
|
@HugoDF How would that be a problem using it with Alpine though? I mean, once I have the object instance passed to |
Like I said I can't think of any, Alpine.js is pretty good with binding |
@calebporzio Could you weigh in on this? |
Put it another way, if Alpine manages to keep 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. |
Hi, again @HugoDF. I'm reopening this issue because I spotted a problem using the latest 2.1.2 version. |
The reactivity engine was switched in v2.1 you're probably going to need to switch to objects |
I'm doing this right now, thanks for confirming. |
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 |
I see... |
@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:
When using a ES6 class, its prototype is custom and different from Object. 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: salesforce/observable-membrane#42 (comment). 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 |
@SimoTod At least that comment sparked the conversation on the |
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. |
Hi guys, closing this issue due to inactivity. If you think this feature is a necessity, please open another issue for discussion. |
Hi @ryangjchandler! |
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. |
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 😄. |
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). 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 |
May not be officially supported but there's a hackish kind of way to get it working with es6 classes. It seems that normal instantiation of a class doesn't work but if you make a copy of it instead it works. Also, methods need to use the function keyword below or Alpine won't be able to find the method for some reason: interface IAccordionTrigger {
open: boolean;
toggleTrigger(): void;
}
abstract class AccordionTrigger implements IAccordionTrigger {
public open = false;
public toggleTrigger = function () {
this.open = !this.open;
};
}
class AccordionBottomTrigger extends AccordionTrigger {
public truncatePreviewText = function () {
if (this.open) return false;
return true;
};
}
// Does not work:
window.accordionBottomTrigger = () => {
return new AccordionBottomTrigger();
};
// Works:
window.accordionBottomTrigger = () => {
return { ...new AccordionBottomTrigger() };
};
// Also works:
window.accordionBottomTrigger = () => {
return Object.assign(new AccordionBottomTrigger());
}; |
Yeah, that works because you are deconstructing the class into a literal object. You will lose all the class functions and just inherit the properties though. |
I was having trouble getting the methods to work but when I defined the methods with the function keyword instead, they started working (i.e. Pressing the toggle button was actually toggling the open state and reflecting it in the DOM). So I was accessing all the properties and methods just fine. Anyways this isn't a silver bullet solution. Classes should work without having to do doing anything extra. |
Yeah, what i mean is, if you get a class that you didn't write, with functions defines on the prototype and stuff like that, deconstructing the object will remove functionalities. If you wrote the class but you are just transforming it to an object literal you probably won't get much benefit compared to a normal. Object but yeah, it does work |
Hi everyone, I'm here to complete this discussion with my own research. I wanted to try using Alpine with classes to create components that were a bit advanced, and I thought that we could reassign the value of "this" in the object itself: <div x-data="DropDown">
<button @click="$component.toggle">
Expand
</button>
<div x-show="$component.isOpen">
Content...
</div>
</div> import Alpine from "https://cdn.skypack.dev/alpinejs@3.12.2";
class DropDown {
constructor() {
this.$component = this;
this.isOpen = false;
}
toggle() {
this.isOpen = !this.isOpen;
}
}
Alpine.data('DropDown', () => (new DropDown()));
Alpine.start(); I'd like to make it clear that for the moment, I haven't done any further tests on the component's lifecycle, etc... But I can already access the "Magics": <div x-data="DropDown({ isOpen: false })">
<button @click="$component.toggle">
Expand
</button>
<div x-show="$component.isOpen">
Content...
</div>
</div> class DropDown {
constructor(options) {
this.$component = this;
this.isOpen = !!options.isOpen;
}
toggle() {
this.isOpen = !this.isOpen;
}
}
Alpine.data('DropDown', (options) => (new DropDown(options))); |
Excellent idea, thank you! Have you stumbled upon any problems since then? I'm trying to build my frontend in Typescript + Webpack and to load these modules into Alpine. So far, so good, but I'm in the very beginning now. |
So far, I haven't had any problems with this "architecture". From time to time, if I have nested components, I rename the And sometimes it's handy: I have a |
The approach works well, but it should be noted that you also need use the class DropDown {
constructor(options) {
this.$component = this;
this.isOpen = !!options.isOpen;
}
complexMethod() {
this.methodA(); //<- no worky
this.$component.methodA(); //<- works
}
methodA() {
}
} |
It doesn't work if you make the call directly. I think it has to do with the way the Proxy is sent. |
Hi! I think the constructed objects are "plained" before proxying. So, the prototype exclusive members (methods, private members, etc.) declared are lost. Non-private methods can be copied from prototype, as shown below, but private members cannot be used. This seems to work for me: <body x-data="test">
<pre x-text="prop"></pre>
<pre x-text="method()"></pre>
<pre x-text="anotherMethod()"></pre>
<script type="module">
import { Alpine } from "./packages.js";
class AlpineClassComponent {
constructor() {
const prototype = Object.getPrototypeOf(this);
for (const prop of Object.getOwnPropertyNames(prototype)) {
if (typeof prototype[prop] !== "function") continue;
this[prop] = prototype[prop];
}
}
}
class Test extends AlpineClassComponent {
prop = "prop";
method() {
return "method";
}
anotherMethod() {
return `another ${this.method()}`;
}
}
Alpine.data("test", () => new Test());
Alpine.start();
</script>
</body> Output:
|
I like the idea, in the end it's a clone of the methods and in the end it's more or less the right thing to do. The proxy is really blocking if you want to do things differently, but this technique works well for me too. |
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.
The text was updated successfully, but these errors were encountered: