Ember.js 1.13.0 and 2.0 Beta Released
We are pleased to announce the release of both Ember.js 1.13.0 and the first beta in the 2.0 series. This comes as the thirteenth cycle of our release process that began after 1.0 was released.
The 1.13 release represents the effort of at least 43 contributors across over 680 commits.
Ember 1.13 is the last release in the 1.x series and the first release that includes the Glimmer rendering engine. It includes a number of deprecations that will ease the upgrade to Ember 2.0, which is due to land in six weeks.
Ember 2.0 beta is the first release in the 2.x series. This means that many features deprecated during the 1.x series will be removed in Ember 2.0. If you encounter any unexpected changes in features not marked as deprecated in 1.13 while testing Ember 2.0 beta, please report them immediately. We would like to fix these unintentional regressions before the final release of 2.0 in six weeks.
The release of Ember 2.0 beta also means that the first features in Ember 2.1 (most notably angle bracket components), are in their final canary stretch. Now is a good time to comment on RFCs and pull requests related to those features.
New Features in Ember.js 1.13
The Glimmer rendering engine, announced at EmberConf and introduced to Ember.js Canary on May 5th, is released today in Ember.js 1.13. Glimmer is the third overhaul of Ember's 1.x rendering layer (wow, right?), and dramatically improves re-render performance in many common scenarios. Additionally it lays important groundwork for the Ember 2.x development model.
We are tremendously excited to bring Glimmer to existing applications as a 1.x compatible minor release. The herculean efforts of the Ember core team, addon authors, and community to achieve this release has been inspiring. Thank you! You are too many to possibly name.
1.13 also marks the final minor release of the Ember 1.x cycle. As such, it contains a number of deprecations ahead of Ember 2.0. Resolving these deprecations in application code aligns that application with Ember's public API in 2.0. In general, Ember apps running 1.13 without causing any deprecation notices to fire should upgrade to 2.0 without changes.
The easiest way to work through deprecations is the Ember Inspector. Thanks to the work of Teddy Zeenny, deprecations will be routed to the "Deprecations" inspector pane, where you can get a grouped list of them as well as the line of code in your app that triggered the deprecation. You can also ask for a full stack trace of any deprecation.
Improved Rerender Performance
Previous iterations of Ember's rendering engine relied on granular observation for efficiency. When a piece of dynamic content was rendered, Ember registered observers, updating the content when the value changed.
While this was reasonably efficient in cases where the developer could use set
(and the array equivalents) to mutate values, it had two related issues:
- This forced developers to represent all changes in terms of granular observers. In many cases this could be extremely awkward. This was especially problematic when working with Arrays, since (for example) representing a sort as a series of mutations is conceptually complex and can be cost-prohibitive.
- Ember itself was extremely inefficient when an entire object or array was replaced, despite the fact that this was often the most natural way to represent the change. This meant that while it was usually possible in theory to "just re-render" a component, it was, in practice, cost prohibitive (to say the least).
To address these issues, Glimmer adopts a value-diffing strategy, using a virtual tree of the dynamic areas of the DOM. This means that even if the original data structure (for example, an array) is completely replaced, the DOM is not updated unless the resulting rendered content has changed.
When updating an array with a new array (because you got a new array from the server, or because you produced a new array through .sort()
), you will see a large improvement in performance, making this kind of replacement plausible in Ember.
Notably, the Ember strategy continues to support efficient updates via value observation, which we expect to be useful when communicating with services and models.
Glimmer's hybrid model can opportunistically take advantage of explicit mutation (via set
) when it is used, while also supporting efficient re-renders of entire data structures, updating only the DOM nodes that need to be changed.
The result is a stunning improvement in many rerender cases.
We would like to thank React for showing that full re-renders can be made efficient. While we did not use their precise Virtual DOM approach, their work with Virtual DOM was extremely influential to our approach in Glimmer.
Thanks to @wycats and @tomdale for their continued focus on improving Glimmer's performance, and to LinkedIn and Bustle for their generous sponsorship of this work.
Component Lifecycle Hooks
A number of new component lifecycle hooks have been introduced to Ember 1.13. Using these hooks allows you to write data down, action up (DDAU) style components today, despite the two-way data binding of curly components.
On first render (in order):
didInitAttrs
runs after a component was created and passed attrs are guaranteed to be present. In Ember 1.13, the attributes will be available asthis.get('attrName')
.didReceiveAttrs
runs afterdidInitAttrs
, and it also runs on subsequent re-renders, which is useful for logic that is the same on all renders. It does not run when the component has been re-rendered from the inside.willRender
runs before the template is rendered. It runs when the template is updated for any reason (both initial and re-render, and regardless of whether the change was caused by an attrs change or re-render).didInsertElement
runs after the template has rendered and the element is in the DOM.didRender
runs afterdidInsertElement
(it also runs on subsequent re-renders).
On re-render (in order):
didUpdateAttrs
runs when the attributes of a component have changed (but not when the component is re-rendered, viacomponent.rerender
,component.set
, or changes in models or services used by the template).didReceiveAttrs
, same as above.willUpdate
runs when the component is re-rendering for any reason, includingcomponent.rerender()
,component.set()
or changes in models or services used by the template.willRender
, same as abovedidUpdate
runs after the template has re-rendered and the DOM is now up to date.didRender
, same as above.
Note that a component is re-rendered whenever:
- any of its attributes change
component.set()
is calledcomponent.rerender()
is called- a property on a model or service used by the template has changed (including through computed properties).
Because of the Glimmer engine, these re-renders are fast, and avoid unnecessary work.
Closure Actions
In Ember 1.x, the actions system used bubbling as a solution for passing user behavior to a parent scope. For example, when clicking a button an action might bubble through several controllers then be handled on a route.
Action bubbling was difficult to debug, and plagued by an inability to have a return value (since the return value of an action handler controlled further bubbling).
Ember 2.x is component-driven, and replaces action bubbling with a function-passing solution. This greatly simplifies working with actions (they are functions, after all), enables return values, and introduces some powerful new currying capabilities.
For example, action submit
is passed to my-component
where it is called upon
click:
// app/controllers/index.js
import Ember from "ember";
export default Ember.Controller.extend({
actions: {
setName(name) {
model.set('name', name);
}
}
});
// app/components/my-component.js
import Ember from "ember";
export default Ember.Component.extend({
click() {
this.attrs.submit(this.get('name'));
}
});
Actions:
- Can be passed multiple arguments
- Return a value. For example
var result = this.attrs.submit();
- Can curry. For example
submit=(action 'setName' 'Sal')
would pass"Sal"
as the first argument tosetName
whensubmit
is called. Actions can curry multiple times, adding arguments at each scope. For examplesubmit=(action attrs.actionPassedIn someProp)
adds an argument to any already curried ontoactionPassedIn
.
Additionally the action
helper has two options:
(action 'save' target=session)
would look at theactions
hash on thesession
object instead of the current context.(action 'save' value="currentTarget.value")
would read the pathcurrentTarget.value
off whatever the first argument to the called action is. This is handy for destructuring objects passed as the first argument (like DOM events).
Note: Angle Bracket Components
Ember 2.1 will (likely) ship with angle-bracket components, which will introduce one-way data flow by default, and provide an opt-in for two-way data flow. Existing components maintain the existing behavior (for compatibility). While the internals of Ember 2.0 support a distinction between one-way and two-way bindings, that distinction will remain largely internal until Ember 2.1.
New Ember.js Helper API
Ember's helper story prior to 1.13 has been inconsistent and neglected. In 1.13, we're introducing a new API for writing helpers along with a set of constraints and features informed by real-world experience.
Ember helpers:
- Represent a single value
- Do not manage DOM or control flow
- Can recompute themselves, similar to how a component can rerender
- Can optionally access services
- Do not require a dash
Helpers come in two flavors. The first is a function-based API we call a shorthand helper. For example, this shorthand helper joins a first and last name:
// app/helpers/full-name.js
import Ember from "ember";
export default Ember.Helper.helper(function(params, hash) {
return params.join(' ');
});
This helper can be used in a variety of contexts:
Helpers receive two arguments: params
are the ordered params passed to a
helper, and hash
contains the key-value options, for example title="Mr."
.
This function version satisfies a wide array of use-cases and is quite powerful. In general, you should use this helper form unless you have a strong reason to do otherwise.
Some helpers, especially in addons, may require access to other parts of Ember (services), and some control over their own invalidation and recomputation. In these cases, a helper class can be used.
For example, this helper computes a name based on a name-builder
service. It
also recomputes whenever the isAnonymized
state on that service changes:
// app/helpers/full-name.js
import Ember from "ember";
export default Ember.Helper.extend({
// This service name is only an example
nameBuilder: Ember.inject.service(),
compute(params, hash) {
return this.get('nameBuilder').build(params, hash.title);
},
rebuildName: Ember.observer('nameBuilder.isAnonymized', function() {
this.recompute();
})
});
For more information on the new helper API please see RFC #53 on helpers and RFC #58 on dashless helpers. Thanks to the addon community (especially @jamesarosen) for bringing the requirements for this API to our attention and testing changes with little notice. Thanks to @mixonic and @rwjblue for the implementation.
Component Block Info
Ember.js 1.13 introduces two new template keywords that provide reflection on how a component is called.
hasBlock
will be true when a component is invoked in block form. For example
given this component:
Then these two usages would be valid:
Full name:
Full name:
Additionally, hasBlockParams
will be true if the component is invoked
with block params (invoke in block form with as |someParam|
).
Thanks to @mmun and @rwjblue for implementing this feature.
Notable Deprecations in 1.13
In preparation for Ember 2.0, 1.13 introduces many deprecations. These include:
- All view APIs in Ember. See deprecation guide
Ember.CoreView
,Ember.View
,Ember.CollectionView
,Ember.ContainerView
{{view 'some-helper'}}
- The
{{view}}
keyword for accessing properties on a view Ember.Select
and{{view "select"}}
. See deprecation guideEmber.LinkView
in favor ofEmber.LinkComponent
. See deprecation guide
- Options to the
{{#each
helper that trigger a legacy and poorly performing legacy layer. These options are:itemView
,itemViewClass
,tagName
,emptyView
andemptyViewClass
. - The
itemController
argument for{{#each
. - The
bind-attr
helper. Using helpers and HTMLBars-style attribute binding is preferred. - Reading
this.get('template')
to check for a yielded block on components. Instead, use thehasBlock
API. - Non-block param
{{with
- The
view
andviewClass
params for{{outlet}}
Ember.reduceComputed
andEmber.arrayComputed
in favor of plain normal array manipulations. See deprecation guide
Ember 2.0 beta
Last November, @wycats and @tomdale published The Road to Ember 2.0, an RFC summarizing Ember's goals for a 2.0 release. At EmberConf, when some of these changes were already complete and others not begun, they announced our intent to ship 2.0 beta on June 12th.
Together, the features summarized in the 2.0 RFC describe a new way to author Ember applications. Model-View-Controller is replaced by Model-Route-Component-Service. Two-way bindings are replaced by data down, actions up (DDAU).
Ember 2.0 will not introduce the entirety of our improved development model. However it will take significant steps in that direction, and allow the removal of public APIs that have been difficult to maintain while we iterate forward.
Because of the focus on landing migration paths for 1.x codebases in 1.13, 2.0 will have few new features. Among them are:
each-in helper
The each-in
helper allows the iteration of object properties. For example,
given this value for items
:
let items = {
"Item 1": 1234,
"Item 2": 3456
};
The following template will iterate the keys:
<p> : </p>
Note that this helper is unbound. Adding a new property to items
will not
cause a rerender, but .set('items', val)
will.
Thanks to @tomdale and implementing this feature, and to several others for helping push it to completion.
get helper
The get
helper provides a bound way to fetch a single property from an object.
For example given these items:
let items = {
"Item 1": 1234,
"Item 2": 3456
};
The following template display 1234
:
This becomes more powerful when the second argument is a bound path:
Thanks to @jmurphyau for implementing this feature.
Notable Breaking Changes in Ember 2.0
Ember 2.0 will remove a number of public APIs, all of which should have been deprecated in the 1.13 release and have a viable migration path. The Ember Deprecation Guide should provide a clear migration path for commonly used APIs.
During the 2.0 beta cycle we will be removing and disabling already deprecated APIs. Much of this work has not yet started, but the following represents what we believe the breaking changes will be.
Many controller APIs are removed in Ember 2.0. Routeable controllers still exist, but all other uses have been deprecated. This includes:
{{render "some-controller"}}
{{each item itemController="some-controller"}}
- This usage can be replaced by nesting a component inside the item, and by using helpers.Ember.ObjectController
Ember.ArrayController
- The
{{controller}}
keyword needs:
on controllers
All view APIs are removed in Ember 2.0. This includes:
Ember.CoreView
,Ember.View
,Ember.ContainerView
andEmber.CollectionView
Ember._Metamorph
,Ember._MetamorphView
- The
{{view "some-view"}}
helper - The
{{view}}
keyword {{each itemView=
,{{each itemViewClass=
,{{each tagName=
,{{each emptyView=
,{{each emptyViewClass
Ember.Select
and{{view "select"}}
Ember.Checkbox
is not removed, but will become a component instead of a view
The most commonly used parts of the view
API will be supported into the forseeable future via a core-supported addon.
All Handlebars APIs are removed in Ember 2.0. This includes:
Ember.Handlebars.helper
,Ember.Handlebars.makeBoundHelper
andEmber.Handlebars.helper
Ember.Handlebars.compile
Several template helpers are removed in Ember 2.0. These include:
{{bindAttr}}
{{bind-attr}}
(use HTMLBars-style attribute bindings instead){{bind}}
{{template}}
(use{{partial}}
instead){{linkTo}}
(use{{link-to}}
instead){{collection items}}
- Non-block params versions of
{{#each}}
and{{#with}}
- Legacy arguments to
{{#each}}
,{{outlet}}
The following routing APIs are removed:
#hash
paths with no forward leading slash
Other APIs:
Ember.tryFinally
Ember.tryCatchFinally
Ember.required
Ember.Map#remove
Ember.Set
Ember.computed.defaultTo
Ember.DeferredMixin
Ember.Deferred
(useEmber.RSVP.Promise
instead)Ember.reduceComputed
andEmber.arrayComputed
(use plain array manipulation)Ember.Freezable
(use Object.freeze instead)
Additionally, IE8 is no longer supported in Ember 2.x. IE9+ is supported.
Many of these deprecated APIs will be moved into core-supported addons, or have already been moved.