This means that every instance will get its own copy of the initial value. This is very useful and intuitive for the cases where we want to ensure state is not shared with other instances, but it means we have to understand the construction of our instances a bit more.

The example above translates essentially to this:

Classes have no way of modifying their superclass, and following the rules of constructors they must wait for the superclass constructor to be called before touching the instance via this , which in turn means that fields can only be instantiated after the superclass has already set all the values passed into create . It makes sense, but it’s somewhat inconvenient for Ember developers.

There are a few ways of addressing this:

Default values can be set in the constructor instead of as class fields.

Default values can be provided by an initializer. Class fields can be provided an expression which will be run for each instance of the class (like creating a new array or object, for instance).

Decorators like those provided in the @ember-decorators/argument addon can be used to mark the fields as arguments the object will receive, and set the default if one does not exist. We’ll touch on this approach more later.

The difference in placement of fields may seem small, but it has pretty large ramifications in how we write code. There are plenty of benefits to this new behavior, but it definitely takes some getting used to, especially for experienced Ember developers.

Decorators

The ember-decorators addon provides decorators for:

Computed Properties

Component Element Customization

Injections

Actions

Observers and Events (Currently broken with ES Classes)

Ember Data (Currently broken with ES Classes)

We’ll go over each with a brief example and description of the differences and caveats. For more detailed information, check out the API docs for the library.

Computed Properties

The @computed decorator was one of the very first demos of how decorators could be used in Ember. Early examples and the first iteration of ember-decorators , back when it was called ember-computed-decorators , used it directly in the Ember Object model. The option to use decorators on POJOs looks like it may not make it through TC39, but the decorator itself is still around and works beautifully with class syntax:

As you can see, the decorators use native ES getter/setter syntax instead of plain methods. The syntax is meant to be clearer overall and enforce method parameters, but the properties themselves must still be manipulated with get and set . For computeds which have a setter, the decorator only needs to be applied once to either the getter or setter.

The readOnly decorator can also be used to mark computeds as read only, instead of the chained method like in the original syntax. volatile computeds which normally recompute their value each time they are accessed can be replaced with a normal, native ES getter, which does this by default.

Computed Macros

Most of the standard Ember computed macros such as alias , and , or , etc. are available in ember-decorators as well. They can be applied directly to empty class fields:

The notable exception is readOnly , which was omitted to prevent a collision with the existing @readOnly modifier. The solution here is to modify @alias or @reads with @readOnly :

Component Element Customization

Experienced Ember devs are probably familiar with the tagName , classNames , classNameBindings , and attributeBindings properties that can be used to customize a component’s element. These special properties can be a common source of confusion for new developers, and Ember is trying to move away from them in the near future with Glimmer components, but for the time being they are still necessary.

tagName is a special property that needs to be applied before the component initializes in some cases, i.e. on the component’s prototype. The other three are examples of concatenated properties — they append their values to the values of property on the superclass — which we pointed out in the beginning are currently broken in ES classes.

The solution is a combination of class and property decorators:

The class decorators allow you to specify properties of the class in advance, while the property decorators allow you to both declaratively specify the binding and the default value in a single statement.

Injections

The @service and @controller decorators exist to allow you to inject services and controllers into classes. They work very similarly to the existing syntax, the just need to be applied to an empty class field. A service name can be provided to the decorator, or it can infer the name via reflection:

Actions

The actions hash on Ember objects is the most common example of a merged property — one whose values will be merged with the actions hash of the superclass, and so on. Similar to the @attribute and @className helpers, ember-decorators provides an @action decorator which can be applied directly to class methods:

One key difference this causes is that the method exists on the class itself in the ES Class version. This means it can conflict with other lifecycle or event hooks, so be aware of name collisions.

Observers and Events

The @on and @observes decorators will allow you to turn functions into event listeners and observers once the work has been done to fix the issues in Ember.Object. Currently, they don’t work, and one major caveat of them not working is that classes that have existing listeners or observers will not properly override those when being extended — if you have an observer or event listener named foo and you try to override it in a subclass, it will still fire.

When they are fixed, you’ll be able to use them like so:

Ember Data

There are decorators for @attr , @hasMany , and @belongsTo in the ember-decorators library that currently work on the standard Ember Object model using DS.Model.extend , but do not work on ES classes. For the moment, the recommendation is to continue using .extend with Ember Data models.

Mixins

The last major Ember feature we’ll touch on are Mixins. Mixins are an integral part of Ember, they are the foundation of the Ember Object model (quite literally, they’re core to how extend ing works!) However, they are leftover from a time when Javascript didn’t have a class system, and each framework had to invent their own.

Integrating them into ES Classes would require a complete rework of how the the mixin system and Core Object works internally. On top of that, mixins are not an Ember-specific pattern — plenty of other frameworks have implemented them, and more systems will likely emerge as class decorators become standardized. With that in mind, the RFC’s position was that mixins should not be reworked to work with ES class syntax.

If you still really need them, however, you can continue using .extend to mix them in. When extending has been fixed for ES classes in general they will be usable anywhere:

Argument Decorators

The @ember-decorators/argument addon provides a set of decorators that accomplish two things:

Provide a sane way to set defaults on components and other objects via the @argument decorator, addressing the issues brought up by the segment on class fields above. Provide runtime type and invariant validations, inspired by the excellent ember-prop-types library. These validations are completely removed from production builds by default, so they are effectively zero-cost.

Providing Defaults

The @argument decorator marks a field as an argument and sets the default if one hasn’t been set. It takes its name from Glimmer.js, which requires that users make a distinction between arguments and attributes when invoking a component. Arguments get passed into the component, while attributes get applied to the component’s element. The name also implies similarity to a function call, which is a helpful mental model for thinking about components — you are calling them from the template with some arguments, just like a function.

By default, components will throw an error when you attempt to use them with arguments that haven’t been defined. This does not apply to other types of objects, and it can be turned off via an ember-cli option:

Fields marked with the @attribute and @className decorators are also whitelisted, so they won’t throw errors.

Specifying Validations

You can use the @type , @required , and @immutable decorators to specify invariants about various fields, arguments, and attributes. The validations run once at the end of object creation, and in the case of @type and @immutable whenever you attempt to set their values.

Types can either be a string representing a primitive type, a class that the field is an instance of, or a type made using one of the type helpers: unionOf , arrayOf , or shapeOf . Some predefined types are included with the library, including:

Action : Union type of string and Function , and recommended for specifying actions that are passed into components

: Union type of and , and recommended for specifying actions that are passed into components ClosureAction : Alias of Function and recommended if you want to enforce only closure actions

: Alias of and recommended if you want to enforce only closure actions Element : Fastboot-safe alias for window.Element

: Fastboot-safe alias for Node : Fastboot-safe alias for window.Node

Here’s an example showcasing the flexibility of these decorators:

All of these extra validations are stripped from production builds by default, so you won’t have to worry about them impacting the performance of your app. For more detailed usage docs, checkout the documentation.

Legacy Usage

Prior to Ember v2.13, the framework accomplished dependency injections by extending classes a second time and adding the injections. This breaks the ES class constructor function, which in turn breaks class fields.

If you’re on an older version of Ember, you can install the ember-legacy-class-shim:

$ ember install ember-legacy-class-shim

This addon reopens Ember.Object to change the behavior of the extend function when being used on native classes for injection. This does not fix extend for general usage on native classes however, as that requires changes to Ember.Object in the Ember.js core.

What Comes Next

Now that you know how to use ES Classes in your app, you may be curious about what’s coming up next with the evolving spec!

As I noted in the beginning, upgrading to Babel 7 and the latest version of the spec should be interesting, but ember-decorators and @ember-decorators/argument should be able maintain their existing APIs. Fixes for the broken functionality like observers and events are in the works in Ember core, and fixes for the Ember Data decorators should come soon.

There are also some projects in the works to take advantage of the declarative and standardized nature of this syntax to work on better tooling for Ember users in general. The type information provided by @ember-decorators/argument can be used to automatically generate thorough component documentation, including both the arguments each component receives and the actions it sends. At some point the metadata may be usable in better static analysis tools as well (although at that point it may be better to switch to ember-cli-typescript )!

Eventually, as decorators are finalized and accepted into Javascript, RFCs will eventually land in Ember itself for an official set of decorators. The ember-decorators project will likely continue past that, deprecating decorators that are replaced but continuing to support supplemental decorators like those in @ember-decorators/argument .

Putting It All Together

To summarize the new API, here’s an attempt at a not-totally-contrived example component that demonstrates the differences:

Overall the result is clearer and easier to read, the decorators provide context and are self-documenting, and the final component has more levels of safety than before.

In review:

ES Classes can be used with Ember today, as far back as Ember v1.11

Major differences include:

- constructor replaces init

- Class fields are applied to the instance, not the prototype

- Decorators should be used for most Ember functionality, like computeds and service injection

- replaces - Class fields are applied to the instance, not the prototype - Decorators should be used for most Ember functionality, like computeds and service injection Major caveats include:

- Observers and events do not work

- Merged and concatenated properties do not work

- .extend does not work on ES Classes themselves

- Mixins cannot be applied to ES Classes, you must use .extend

- Observers and events do not work - Merged and concatenated properties do not work - does not work on ES Classes themselves - Mixins cannot be applied to ES Classes, you must use If you’re using a version of Ember under 2.13, you should install ember-legacy-class-transform addon

Checkout @ember-decorators/argument for helpful, declarative validations and sane defaults

More class-y goodness is in the pipeline!