Skip to content
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

ES Classes #240

Merged
merged 4 commits into from
Aug 25, 2017
Merged

ES Classes #240

merged 4 commits into from
Aug 25, 2017

Conversation

pzuraq
Copy link
Contributor

@pzuraq pzuraq commented Jul 29, 2017

@rwjblue
Copy link
Member

rwjblue commented Jul 29, 2017

Thanks for putting this together! I'm very much in favor...

@chriskrycho
Copy link
Contributor

chriskrycho commented Jul 29, 2017

First things first: I just want to say this looks great. It's a well-written and well-thought-out RFC, and I'm 100% in favor overall!

I do want to register a strong vote in favor of each of the alternatives raised as possibilities in the Alternatives section, with some reasoning supplied for why in each case. But I'll start by giving some context.

Qualify All The Things

While I profoundly appreciate Ember's strong use of conventions to accomplish things, I find both as a user and especially as someone trying to help others get up to speed on our app, I have a strong preference that things become more explicit when we're deploying new variants of existing APIs. The main complaint I hear from others on my team who come to our Ember app is that it's hard to understand what's going on, because there's so much that just magically happens. And that makes the learning curve harder. That goes especially for things like the way classNames and other things like it are automatically merged together. That magic is powerful when you know how it works, but it's confusing until then, and it can have surprising side effects because it's effective invisible when reading the code.

That sentiment goes double because I'm now the guy leading the charge with ember-cli-typescript: any magic we put in place makes it that much harder to fully support Ember from TypeScript. Now, a qualification to that sentiment is important: Even if we want TypeScript to be a first-class citizen of the ecosystem, right behind JavaScript, we should be careful not to let "easier for TS" dictate design decisions—even if we do want that to be a consideration. My point is simply that if we have two choices which are pretty comparable in terms of the JavaScript consumers' point of view but one is easier for TS, we should probably favor that.

So take all that as background for my position on each of these things.

Why the "Alternatives" are better

TL;DR: less magic in every case, and therefore fewer surprises for anyone reading the code, especially people coming from the broader ecosystem. In other words: all the reasons we're doing this in the first place militate strongly in favor of doing them in the way that provokes the least surprise.

Class Property Initialization

My (and I suspect most peoples') mental model of the behavior of Ember.Object.create() is that it's essentially a static factory method: it instantiates the object with whatever customizations you supply to it. That is the current outcome on the non-class variant, and I can't actually think of a good way to explain the proposed new behavior to another developer. As I understand the proposal, it only has any effect on properties which have not been declared on the class definition. So it's a way of adding properties to an instance of the class, but outside the constructor. The result is both surprising to existing Ember developers and (I suspect) provokes a big "Why would I ever do that?" for people coming new to the ecosystem. (The explanation of how it currently behaves was a good explanation, but it's still rather surprising!)

Making Ember.Object.create() actually behave as a factory which customizes the class in question is less surprising by far in my view.

Edit: see comment below; I changed my mind on this after @rwjblue's request for clarification below prompted me to re-read that section and think about it again.

Merging and concatenating properties

As noted in my "qualifications" section above, I think these are already a rather surprising (albeit powerful) corner of the Ember.Object model, and I think a big part of what makes them surprising is that the merging is entirely implicit – you'd better have read the docs closely and carefully. If you're trying to figure out where a given class on a rendered component comes from, nothing in a component definition even gives a hint that there might be other sources of truth for that particular item. To the possible rejoinder that it's implied by the fact that it'll only happen if you're explicitly mixing in or inheriting from something else: sort of, but only if you know the behavior is possible in the first place, and it's distinct from the way all other properties on an item definition behave. In the proposal as written, classNames and someOtherProperty would behave differently from each other in BarComponent—but that's not apparent at all.

const FooComponent = Ember.Component.extend({
  classNames: ['look-ma-a-foo'],
  someOtherProperty: ['also an array', 'of strings'],
})

class BarComponent extends FooComponent {
  classNames = ['hey-its-a-bar'];
  someOtherProperty = ['these are', 'also strings'];
}

Gladly, the docs cover this well:

Standard CSS class names to apply to the view's outer element. This property automatically inherits any class names defined by the view's superclasses as well.

Still, I think it's better to be explicit:

class BarComponent extends FooComponent {
  @concatenated classNameBindings = ['hey-its-a-bar'];
  someOtherProperty = ['these are', 'also strings'];
}

Explicit is better than implicit isn't a hard and fast rule, but it's a really good guideline for API design—and I think it's especially important when the behavior of one item is different from how everything else that looks like it behaves.

Observers and listeners

I take much the same view here as I do about the merged and concatenated properties. To those considerations, I would add mainly that I increasingly think the framework should discourage the use of both in favor of computed properties and normal actions for the majority of cases. And the docs seem to agree:

Note: Observers are often over-used by new Ember developers. Observers are used heavily within the Ember framework itself, but for most problems Ember app developers face, computed properties are the appropriate solution.

Given that preference, making these more explicit not only has all the wins expressed above, but it's also a slight bump in the work required to use them, and—most importantly—it makes it very explicit that the property is bound in the ways it is.

Summary

Given that the alternative proposals for merged/concatenated properties and observers and listeners are (a) backwards-compatible, (b) opt-in, (c) more in line with the way the rest of the JavaScript ecosystem behaves, and (d) easier to make sure we get right in TypeScript… well, those seem like pretty clear-cut cases to me. I think they're much better choices. And I think the alternative for Ember.Object.create() is far less surprising and easier to understand (and therefore easier to teach, as well).

@rwjblue
Copy link
Member

rwjblue commented Jul 29, 2017

@chriskrycho - Thanks for the detailed and thoughtful response!

Believe it or not, I think that I actually agree with all very nearly all of your points, but I think the position this RFC is taking is still the correct course of action. Very specifically, this RFC is only focused on ensuring that class Foo extends Ember.Object {} functions properly (and yes this means that it has to support any/all of the existing Ember Object Model that the class you are extending from is utilizing) and that it is considered public API from a SemVer perspective. The point I'm making here is pretty subtle, but I think it is very important.

I believe the correct course of action for those still overly magical things (merged and concatenated properties, some bits of evented, observers, etc) is to continue iterating in future RFC's to work through deprecations and proper replacements independently (for each feature). If we try to replace all of Ember's Object model in a single RFC we would never be able to proceed.

@rwjblue
Copy link
Member

rwjblue commented Jul 29, 2017

@chriskrycho - I don't quite grok what you mean in the "Class Property Initialization" section. Can you explain in more detail (possibly with code snippets)?

@chriskrycho
Copy link
Contributor

@rwjblue

Very specifically, this RFC is only focused on ensuring that class Foo extends Ember.Object {} functions properly (and yes this means that it has to support any/all of the existing Ember Object Model that the class you are extending from is utilizing) and that it is considered public API from a SemVer perspective. The point I'm making here is pretty subtle, but I think it is very important.

Totally agreed with that goal. My take was simply: if the alternatives work to support that option… let's do those; it's less to tackle in the future. That said, if the alternatives don't work to support that option, then I 100% agree: defer them, and if/when this RFC is accepted and implemented, write those as follow-ups.


I don't quite grok what you mean in the "Class Property Initialization" section.

No problem! This actually made me go back and reread that section again (and yes, that comes out to three readings!) and I actually changed my mind on reading it again. I think that section could perhaps be clarified just a bit: the idea comes out to *don't use .create() with classes; always just use the constructor instead` – and I 100% agree with that. So skip that part. 😉

@pzuraq
Copy link
Contributor Author

pzuraq commented Jul 29, 2017

So to be clear, create is still considered the public API for creating classes, at least for those which are used in Ember. Another unmentioned part of the RFC (which should be added) is that finalization of the class must be moved out of the constructor to the create method - things like chains being finished. This is so that class properties can be set before chains are finalized, so they won't trigger observations and whatnot (although if set/get aren't used will that matter?)

The current proposal is that options passed to create would also be passed into the constructor function, giving users an opportunity to modify them in the constructor before they are set on the class. Class properties would still "win" in this situation if there was a conflict between properties passed in and those defined on the class.

The alternative that @chriskrycho is mentioning is to instead update create such that it essentially does this (simplified):

static create(args) {
  const klass = new Class();

  for (arg in args) {
    klass[arg] = args[arg];
  }

  finishChains(klass);

  return klass;
}

So whatever would be passed to create would win, which is analogous to how it works today.

@rtablada
Copy link
Contributor

From a documentation and instruction side of things, while I agree that the main path should be to continue using .extends (at least for the time being). I think that there should be some documentation around how to use ES2015 class syntax in the official docs when true support lands based on this RFC.

I think including it in the official API as listed here and not documenting it would be a cause for confusion.

So, my thoughts would be to add documentation for Using ES2015 Classes with Ember Objects to the The Object Model section documenting the differences between .extends and class Foo extends with Ember Objects.

To clarify, this wouldn't be the pattern shown for the rest of the guides but the option would be documented in the Object Model section.

@simonihmig
Copy link
Contributor

  1. first of all thanks as well to @pzuraq for this great RFC! Absolutely support embracing ES6 classes!
  2. very much agree with @chriskrycho that reducing certain parts of Ember magic makes sense. Especially those that come quite unexpected. Probably nobody complains about the fact that putting a component module in a certain place in the folder structure will "magically" make this available to the DI container and thus any template, without having to write hundreds of lines of DI config. Because this feels quite natural and in line with expectations. But certain bits that were mentioned here just come so unexpected, that it is hard to reason about them without actually knowing the magic. But yes, all of this certainly does not need to be solved in this RFC or any other singer RFC...
  3. so I think when we embrace new JS features as first class citizens in Ember, we should make sure that we do not introduce new unexpected particularities in the way they are used in the Ember ecosystem. Newer frameworks with a fresh start, that don't carry the "legacy" Ember does, are amongst other reason popular because they use just "vanilla JS". So how long our journey from "legacy Ember" to "vanilla Ember" might even take, we should make sure that we end up in a world that is as little ember-ish and as much vanilla-ish as possible (sorry for any pain caused by that sentence! 😬)
  4. that being said, I would support the alternative in regards to class properties. I would expect, that class properties used as defaults would "just work", and that the alternative (they win over args of .create()) would feel like a bug to non-Ember folks. Also being kind of forced to define defaults in the constructor as suggested (if they are meant to work consistently) would be pretty much on the ember-ish side vs. vanilla-ish
  5. while other parts might be fleshed out in separate RFCs, the semantics of .create() regarding ES6 classes need to be finalized in this RFC, as changing that later would introduce a breaking change, right?
  6. I don't know if that is being meant to be covered by this RFC, but in either case maybe this could be clarified: the given examples always assume that ES6 classes are defined at the very end of the inheritance chain. But are they supposed to also work in the middle of the prototype chain? My understanding is that you cannot .extend() from an ES6 class currently, right? Should this work when they become public API as part of this RFC?
  7. If not, they would not feel as real first class citizens. For example I could not write components in an addon as ES6 classes, because I cannot assume that they are not .extended by the addon's consumer. And on the other hand the consumer should not need to know how they were implemented to extend them. If that should work, then great! But even more important then that .create() works in a consistent way (cannot assume they are instantiated using new vs. .create())

@rwjblue
Copy link
Member

rwjblue commented Jul 29, 2017

@rtablada - While I am not opposed to a small paragraph or note suggesting that Ember's object model works with standard ES such as class Foo extends Ember.Object {}, but I would be opposed to a full section as I believe that having the guides be disjointed with multiple syntaxes will make them a bit harder to reason about. Also, there are still a number of common pitfalls (to be addressed by future RFCs) that need to be ironed out before updating the entirety of the official guides and API documentation to use classes.

@rtablada
Copy link
Contributor

@rwjblue I'm not suggesting to update the whole guides as I had mentioned. Instead I was recommending a section under the Ember Object Model heading that shows that works, explains the pitfalls, and likely links to ember-decorators for better support.

@rtablada
Copy link
Contributor

rtablada commented Jul 29, 2017

One thing I'm not sure is quite covered here is the use of the new keyword compared to the .creates static method.

I think that by introducing the idea of ES2015 classes it also makes users want to use the new keyword along with these classes.
People that are used to ES2015 classes will be more likely to want to use new MyClass to instantiate their objects and IMO there should be more explanation of the subtle differences between this and MyClass.create.
Is there any downside to this that needs to be documented or should it "Just Work ™️"?


Clarification: I don't expect this RFC to make new work just like create, just as we introduce ES2015 classes as a public API, we need to identify the possible speedbumps and pitfalls inherit to these new public APIs.

@rtablada
Copy link
Contributor

@rwjblue I was thinking after "Enumerables" so that is quick to reference.
But, as you pointed out: we don't want to change the train of thought in the middle of a section on the basics of the Object model.

screen shot 2017-07-29 at 4 05 54 pm

@rwjblue
Copy link
Member

rwjblue commented Jul 29, 2017

@simonihmig

  1. so I think when we embrace new JS features as first class citizens in Ember, we should make sure that we do not introduce new unexpected particularities in the way they are used in the Ember ecosystem. Newer frameworks with a fresh start, that don't carry the "legacy" Ember does, are amongst other reason popular because they use just "vanilla JS". So how long our journey from "legacy Ember" to "vanilla Ember" might even take, we should make sure that we end up in a world that is as little ember-ish and as much vanilla-ish as possible (sorry for any pain caused by that sentence! 😬)

Totally agreed. One thing to note here, however, is that our "legacy" is one of our strongest selling points: stability with an upgrade path to the future. If for example, we decided to drop support for object model features that are currently heavily depended on, many of our users would be completely stuck and unable to upgrade. It is very important that we do not bifurcate the community during these sorts of changes, which is why this is being handled in small digestible parts.

  1. that being said, I would support the alternative in regards to class properties. I would expect, that class properties used as defaults would "just work", and that the alternative (they win over args of .create()) would feel like a bug to non-Ember folks. Also being kind of forced to define defaults in the constructor as suggested (if they are meant to work consistently) would be pretty much on the ember-ish side vs. vanilla-ish

I totally disagree with you here. We are actually embracing what is done in vanilla JS for class properties. Consider the following:

class Foo {
  derp = "hi!";
  constructor(value) {
    console.log(this.derp); // => undefined
    this.derp = value;
  }
}

let foo = new Foo('bye!');
console.log(foo.derp); // => hi!

As you can see in the proposal here (which was just moved to stage 3 this week actually) the constructor is ran before the class fields are applied to the instance.

If you want to accept an argument but fallback to a default value, you simply have no choice but to use the constructor.

  1. while other parts might be fleshed out in separate RFCs, the semantics of .create() regarding ES6 classes need to be finalized in this RFC, as changing that later would introduce a breaking change, right?

Agreed.

  1. I don't know if that is being meant to be covered by this RFC, but in either case maybe this could be clarified: the given examples always assume that ES6 classes are defined at the very end of the inheritance chain. But are they supposed to also work in the middle of the prototype chain? My understanding is that you cannot .extend() from an ES6 class currently, right? Should this work when they become public API as part of this RFC?

Great point! I believe that it would work properly with the changes listed in this RFC, but we should definitely state that in the RFC specifically.

@rwjblue
Copy link
Member

rwjblue commented Jul 29, 2017

@rtablada (re #240 (comment)) -

At this time we are not suggesting a change in Ember's DI system (e.g. Ember will continue to call the static create method to build instances).

However, with the changes listed in this RFC you would be able to use the following:

class Foo extends Ember.Object { }
let f = new Foo({ some: 'prop', goes: 'here' });

The main caveat (that we should continue to explore) is how we indicate to computed properties and observers that the user-land constructor is completed, and that we should begin listening to change events. This is currently done in the constructor just after init is called, but this RFC proposes to move that stage into the static Ember.Object.create method. Without that change, calling super(...arguments) in your custom classes constructor would enable change events and any initialization you do in the constructor would cause CP's and observers to fire (which is not ideal).

Moving the enabling of change events to the static create method is fine for existing Ember.Object users and for any objects instantiated by the DI system. For objects not created by the DI system (either via owner.lookup or owner.factoryFor(...).create()), we would need to provide a method that would need to be called to enable change events.

Seems good to update the RFC to at least mention this.

@rwjblue rwjblue self-assigned this Jul 29, 2017
@simonihmig
Copy link
Contributor

simonihmig commented Jul 29, 2017

@rwjblue

One thing to note here, however, is that our "legacy" is one of our strongest selling points: stability with an upgrade path to the future.

Absolutely, no question about that! There is a reason I joined the train somewhere @1.0.0-rc.x and never thought about leaving! 😉

As you can see in the proposal here the constructor is ran before the class fields are applied to the instance.

Oh, was not aware of that, thanks for pointing that out!

However I still think that does not rule out implementing .create() in the alternative way, does it? I guess it depends on the way users would normally expect it to work with classes, like...

return new Class(props);

or like

let instance = new Class();
Ember.setProperties(instance, props);
return instance;

@cibernox
Copy link
Contributor

cibernox commented Jul 30, 2017

I'll also drop my view.

I very much agree with @chriskrycho on the fact that I'd like to move concatenated properties, mixins, observers and listeners and other somewhat obscure features to some "official" decorator library.

The reasons are the same: Less magic makes Ember easier to learn and more aligned with other frameworks. I don't fully buy the idea that class MyClass extends Ember.Object has to support all the features that the classical object model does.

In the same way that angle-bracket were added and then removed, and one of the reasons for that was because we wanted to take that unique opportunity of adding new way if invoking components to also change the semantics of components in a backwards incompatible way (for the better) and from such effort Glimmer.js was born, I think we could take the unique opportunity of switching from MyClass.create() to new MyClass (I'd enforce/prefer new over the static .create) to cut with the past and "extract functionality out of the core".

We could say "If you use class MyClass extends Ember.Object and new MyClass you are opting-in to a smaller, lighter object model that requires you to be explicit and use decorators to get some behaviour that is built in when using Ember.Object.extend.

I don't know if this might present some problem for interoperability. Options for interoperability are:

  • class MyClass extend ClassCreatedUsingObjectExtend
  • MyClass = ClassCreatedUsingES6.extend()

If we make the ES6 classes have less features, can then a user decide to use again ES6DefinedClass.extend and have the features that ES6 classes lack? If that is not possible, can users be warned that once an ancestor is defined with ES6 syntax, certain features are not available anymore or force them to continue using ES6 classes for that hierarchy?

I just want to highlight that this change in syntax offers the same opportunity to rethink our object model than angle-bracket components offered to introduce components with new semantics. I appreciate backwards compatibility as much as anyone else, but it's worth considering using this to break with the past in certain edge cases if that is for the best long term.

In summary, I'd be confortable with making ES6 classes be "the object model of Ember 3.0" and the classical object model be maintained for compatibility. I don't think we will ever face a better syntactical change to improve the object model in a backwards incompatible way if needed.

@cursedquail
Copy link

cursedquail commented Jul 30, 2017

My only worry is that using new is currently possible:
screenshot_20170730_181520

@rtablada
Copy link
Contributor

@cibernox with the compatibility scheme for Ember I think it would have to be the Object model for Ember 4.0 to keep up with compatibility concerns.

@rtablada
Copy link
Contributor

rtablada commented Jul 31, 2017

@NLincoln that's a good point that using new with Ember.Object is currently possible, but the differences aren't well documented (from what I can find) and there are some edgecases that create resolves.

@dwickern
Copy link

dwickern commented Aug 3, 2017

I can't 👍 this enough. This change will improve IDE tooling and make Ember+typescript a much better fit.

I'd like to add that method overrides should use ES6 super instead of this._super

export default Ember.Component.extend({
  didInsertElement() {
    this._super(...arguments);
  }
});
export default class extends Ember.Component {
  didInsertElement() {
    super.didInsertElement();
  }
}

@rwjblue
Copy link
Member

rwjblue commented Aug 11, 2017

We discussed this in the Ember core team call today. We are generally all in favor of this moving forward and decided to move this to final comment period.

@workmanw
Copy link
Contributor

workmanw commented Aug 12, 2017

First off, I'm very 👍 on this.

One topic not discussed here is reopen / reopenClass.

Finally, I know the spirit in which it was meant, but I think we should stop short of calling Mixins, Observers, Listeners and Concatenated properties "obscure features" and "overly magical". It's one thing to call them advanced features, it's another thing to implicitly suggest they're bad and shouldn't be used. They've been very widely used.

Observers: 1683 usages across 499 addons: Ember Observer
Mixins: 1788 usages across 533 addons: Ember Observer
Listeners: 6606 usages across 1237 addons: Ember Observer
** The Listeners regex might have a larger margin of error.

@pzuraq
Copy link
Contributor Author

pzuraq commented Aug 12, 2017

Mixins are one thing. They have plenty of issues, for instance with ensuring call order for functions that are overriden (what does _super of a mixin call? What happens in a class that has several mixins that all implement didInsertElement and all call _super?), but the overall issue for this RFC is that they won't be an Ember specific concept any more, but an ES class concept in general.

I agree that Listeners/Observers are not a bad pattern. When used correctly, they are incredibly powerful, and concepts like Promises/computeds do not always fully close over them. The issue here is that they come out of the box, standard on every object. This adds extra weight to every single Ember object, and encourages improper usage of a powerful, low level pattern.

I disagree with concatenated/merged properties. They are highly magical, and like observers/events add weight to every object. However, unlike observers/events, they are very easily solved with a simple decorator, as in the example in the RFC.

Overall, the argument (for future RFCs, not this one) isn't to take any of these features away. It's to split them out into explicit opt-ins, ones that could likely be shared with the larger Javascript community.

@rwjblue
Copy link
Member

rwjblue commented Aug 12, 2017

@workmanw:

One topic not discussed here is reopen / reopenClass.

Agreed, this was not mentioned. There are two different aspects that are important here:

Does the presence of .reopen and .reopenClass fundamentally change the interop system with native ES classes?

To support this we will need to move more of the current object model to "native" ES likely meaning a move away from .ClassMixin and .PrototypeMixin towards mutating the prototype directly.

Is it likely that .reopen / .reopenClass are concepts that will carry over in a "normal" ES class based world?

Probably not. While I think it is important for interoperability that we make sure these methods work properly, I do not think .reopen and .reopenClass are concepts that most users should have to learn and understand.


Finally, I know the spirit in which it was meant, but I think we should stop short of calling Mixins, Observers, Listeners and Concatenated properties "obscure features" and "overly magical".

It seems clear to me that we must continue re-aligning the object model away from the custom bespoke system that we were forced to create when the underlying language lacked so many fundamentally required features towards leverage the common solutions that have been added to the language itself. It is quite likely that this migration will eventually mean some of our current object models features will need to be deprecated. We are absolutely not suggesting in this RFC that any new deprecations be added. Any new object model deprecations will almost certainly go through a deprecation RFC process.

The specific terms "obscure" and "magical" are a bit subjective, if those terms seemed offensive to folks, I am definitely sorry. In my comment I was using "magical" to refer to features whose underlying implementation so complicated that even a core team member (me 😝 ) cannot understand them without a multi-hour deep dive...

@workmanw
Copy link
Contributor

workmanw commented Aug 12, 2017

@rwjblue

if those terms seemed offensive to folks, I am definitely sorry

No, it definitely wasn't offensive. But they are very powerful and very useful features. In particular Mixins and Observers. These are features found in many languages and frameworks. I think they've gotten a pretty bad rap in Ember because they were incorrectly used in lieu of proper framework solutions (e.g. observing values before we had promises). "When all you have is a hammer, everything looks like a nail."

@rwjblue
Copy link
Member

rwjblue commented Aug 13, 2017

@workmanw:

But they are very powerful and very useful features. In particular Mixins and Observers.

Totally agreed on both accounts!

RE: Mixins, this RFC is specifically suggesting that we should embrace native ES features (ES classes today do not support "mixins" like Ember has). Ember.Mixin.create will definitely still be a supported feature for the foreseeable future (in Ember.Object.extend() mode), but we should likely also consider pushing for a solution in spec land. See ember-decorators/ember-decorators#115 (comment) for some additional discussion around this topic.

@luxzeitlos
Copy link

If we get this, wouldnt this mean we can use mixing like this:

class Foo extends Ember.Object.extend(myMixin){}

@dwickern
Copy link

@luxferresum Yes, that's equivalent to

const BaseClass = Ember.Object.extend(myMixin);
class Foo extends BaseClass {}

@rwjblue
Copy link
Member

rwjblue commented Aug 25, 2017

We discussed this at the Ember core team meeting today. Lets do it. 🎉 👏

@rwjblue rwjblue merged commit 9ee500c into emberjs:master Aug 25, 2017
@bartocc
Copy link

bartocc commented Aug 28, 2017

is this normal the merged PR file is still named "0000-es-classes.md" ?

This name positions de file at the top of the "text/" dir while it is the last PR that has been merged.

Forgive me if this is a dumb question since I am not very familiar with the RFC's merge process ;-)

@rwjblue
Copy link
Member

rwjblue commented Aug 28, 2017

You are right, I was supposed to update that before merging 😱. Updated in 0af0bbe.

@championswimmer
Copy link

championswimmer commented Feb 20, 2018

@rwjblue @chriskrycho

Ok not sure where to ask this, think this is probably appropriate.

Using ember-source and ember-cli 3.0

If have this Mixin -

// promise-resolver.js
export default Mixin.create({})

And I do this in my app

// in my app
import PromiseResolverMixin from 'ember-promise-utils/mixins/promise-resolver';

    let PromiseResolverObject = Ember.Object.extend(PromiseResolverMixin);
    let subject = PromiseResolverObject.create();

It works perfectly.

But if we want to use Typescript or ES6 classes

// promise-resolver.ts
export default class PromiseResolverMixin extends Mixin<PromiseResolverMixin> {}

or

// promise-resolver.js
export default class PromiseResolverMixin extends Mixin {}

and then use the same code as above

// in my app
import PromiseResolverMixin from 'ember-promise-utils/mixins/promise-resolver';

    let PromiseResolverObject = Ember.Object.extend(PromiseResolverMixin);
    let subject = PromiseResolverObject.create();

We get the error

Error: Assertion Failed: Expected hash or Mixin instance, got [object Function]
                at new EmberError (http://localhost:7357/assets/vendor.js:24127:25)
                at Object.assert (http://localhost:7357/assets/vendor.js:24370:15)
                at Mixin.reopen (http://localhost:7357/assets/vendor.js:38773:161)

I get it, that the class itself is not a Mixin instance.
Well, and good. Let's change it to -

// in my app
import PromiseResolverMixin from 'ember-promise-utils/mixins/promise-resolver';
let subject = new PromiseResolverMixin()

doesn't work

also, this

// in my app (in .ts file)
import PromiseResolverMixin from 'ember-promise-utils/mixins/promise-resolver';

class PromiseResolver extends Ember.Object<PromiseResolverMixin> {};
let subject = new PromiseResolver();

doesn't work either.

I am kinda stumped here, how best to use ES6 class definition for Mixins ?

@chriskrycho
Copy link
Contributor

In short, you can’t—and I wouldn’t try! The semantics just don’t map in well at this point.

I also strongly encourage people to avoid mixins at this point. Mostly they just cause pain.

To use a mixin defined with Mixin.create() in a class, you can do this:

import Component from '@ember/component';
import SomeMixin from 'the/path/to/it';

export default class MyComponent extends Component.extend(SomeMixin) {
  // normal class definition
}

Note that you may still see type errors when you do that in TypeScript – happy to talk about those in an issue on https://github.com/typed-ember/ember-cli-typescript or in the #topic-typescript room in the Ember Slack.

@championswimmer
Copy link

@chriskrycho

Sounds fair.

What is an alternative to Mixin then ??

MyMixin extends Component
MyComponent extends MyMixin

I guess that's the correct approach ?

@GCorbel
Copy link

GCorbel commented Feb 20, 2018

@championswimmer I personally prefer to have Utils classes. For example, if you want to create something to archive a model, instead of create a mixin and do myModel.archive(), I prefer to create a class and do Archiver.call(myModel). I know this is doesn't solves the problem you have but it's a good alternative, it's clean and it follows the "Single Responsibility Principle".

@chriskrycho
Copy link
Contributor

Also worth note: you often don't need classes at all to solve this. In our experience, most stuff that we use mixins for can be replaced by plain old modules and pure in/out functions.

@jacobq
Copy link
Contributor

jacobq commented Aug 24, 2018

Where can one find more documentation about this? For example, when defining a model like ember g model foo, do I need to use Model.extend({...}) like the API docs show as an example?

import DS from 'ember-data';
const { Model } = DS;

export default class FooModel extends Model.extend({
  // When do things need to go here?
  prop1: DS.attr(),
}) {
  // Will this work?
  prop2 = DS.attr();
}

@pzuraq
Copy link
Contributor Author

pzuraq commented Aug 24, 2018

@jacobq you can find the most up to date documentation in the ember-decorators project's documentation. There is no official Ember documentation about using native classes yet because native classes have not officially shipped, and there are still potential changes that could change the way native classes are used. See RFC #337 for more details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Final Comment Period T-framework RFCs that impact the ember.js library
Projects
None yet
Development

Successfully merging this pull request may close these issues.