Deprecations Added in Ember 1.x

What follows is a list of deprecations introduced to Ember during the 1.x cycle.

For more information on deprecations in Ember, see the main deprecations page.

Deprecations Added in

§ Access to Instance Initializers

until:
id: instance-initializers

Deprecate Access to Instance Initializers

When we initially designed Ember, we only had the use case of apps that run in the browser in mind. In that world, you would load your app and then it would run until the user closed the tab or navigated away.

For testing, we quickly realized that we would need some way to "reset" the application between each test without reloading the page and starting from scratch. To that end, we isolated all of the state in an application into an object called the container that could be thrown away after each test had completed.

We wanted a shared way for both apps and add-ons to initialize applications when they booted and loaded. In particular, it was important for people to get access to that container to make objects available to the app at runtime. To do so, we introduced the initializer API.

For example, Ember Data uses this API to ensure that all of your controllers and routes have access to the store object.

As we have been working on the FastBoot effort to allow Ember apps to run on the server, we realized that the assumption that there would only be one instance of the app running at once no longer held.

In particular, we realized that "app boot" was actually two distinct phases:

  1. Loading all of your application code and registering objects with the application (such as the store in the Ember Data example)
  2. Instantiating objects when the app starts running

When the application boots up for the first time, we need to run Step 1 above, but it would be wasteful to run it for every request going to your FastBoot server.

We also realized that a huge number of real-world initializers were simply registering classes and injections, while a smaller fraction of initializers are doing work that truly needs to be done per run.

By separating out these two kinds of work, we could improve the performance of cases where a single application was used multiple times, each with a fresh set of state.

This applies to FastBoot, of course, but it also applies to integration tests. Today, integration tests spend time executing code in initializers that don't change between test runs, simply because of the possibility that the initializer may also set up instances.

Thankfully, the initializer infrastructure already clearly marks the boot-up phase of your application (both in your own code and in add-ons), so we only need to make a small tweak to initializers for FastBoot to work.

This change makes the difference between initializers that set up code and initializers that set up instances explicit.

The old App.initializer API now receives a registry, which can be freely used to set up code using the register API and the various inject APIs.

A new App.instanceInitializer API receives an ApplicationInstance object. When migrating code, the most important property on the ApplicationInstance is container, which you can use to get new instances and set state on them.

Migrating code to the new structure is fairly mechanical. Change code that looks like:

App.initializer({
  name: "clock",

  initialize: function(container) {
    container.register("clock:main", Clock);
    var clock = container.lookup("clock:main");
    clock.setStartTime(Date.now());
  }
});

To:

App.initializer({
  name: "clock",

  initialize: function(registry) {
    registry.register("clock:main", Clock);
  }
});

App.instanceInitializer({
  name: "clock",

  initialize: function(instance) {
    var clock = instance.container.lookup("clock:main");
    clock.setStartTime(Date.now());
  }
});

Some things to remember:

  1. All instanceInitializers run after initializers and do not share a namespace. You can use the same name in both, and don't have to use before or after to make sure that instance initializers run second.
  2. The deprecated registry.lookup works by maintaining a 1:1 mapping between Application and a default ApplicationInstance. This is fundamentally incompatible with FastBoot, and you will need to clear this deprecation warning before you will be able to use any of the FastBoot features.

While FastBoot and the improved testing performance have not yet landed, these changes allow us to continue work on those APIs after Ember 2.0. It also future-proofs Ember for more situations where a single Application has multiple runs over its lifetime.

Deprecations Added in 1.7

§ .then on Ember.Application

As part of the Ember.DeferredMixin deprecation, using .then on an Ember.Application instance itself has been deprecated.

You can use the ready hook or initializers to defer/advance readiness instead.

§ Ember.DeferredMixin and Ember.Deferred

Ember.DeferredMixin and Ember.Deferred have been deprecated in favor of using RSVP.Promises.

§ Observing container views like arrays

ContainerViews have been observable as arrays, where the items in the array are childViews. This introduces complexity into container views despite the feature being a rarely used one.

Deprecations Added in 1.8

§ Global lookup of views

Previous to Ember 1.8, views would commonly be fetched from the global scope:

{{view App.SomeView}}
{{each item in items itemViewClass=App.SomeView}}

Since Ember 1.8, views are more appropriately resolved on the application via strings:

{{view "some"}}
{{each item in items itemViewClass="some"}}

They may also be fetched via a binding:

{{view view.someViewViaTheCurrentView}}
{{each itemViewClass=someViewViaAControllerProperty}}

In general, it is recommended that your Ember application avoid accessing globals from a template.

New usage of Ember.Select

Most of Ember's provided views are already accessed via helpers. For example, the Ember.TextField view is used via the input helper.

The Ember.Select view has not been upgraded to have a helper. Instead, it was suggested that you call it via the global class name:

{{view Ember.Select content=manyItems}}

Since this lookup is now deprecated, the select view has been registered on an application as select. The new usage is:

{{view "select" content=manyItems}}

See the updated Ember.Select documentation and the built-in views guide for more details and examples.

Ember.js libraries and plugins

If the code triggering this deprecation is being fired from a library, that library may need to update its suggested usage.

One solution for such a library is to provide mixins instead of classes:

// usage is {{view "list"}}
var App.ListView = Ember.View.extend(ListView);

A more advanced solution is to use an initializer to register the plugin's views on the application:

// usage is {{view "list"}}
Ember.Application.initializer({
  name: 'list-view',
  initialize: function(container, application) {
    container.register('view:list', ListView);
  }
});

More details on how to register an Ember.js framework component are available in the initializer API documentation and the dependency injection guide.

§ Hash Location Paths Without Leading Slashes

Prior to this release, if you were using location: 'hash' (which is the default), you were able to link to a route with a location.hash that didn't contain the expected leading forward slash. e.g. #foo instead of the correct #/foo. Very few, if any, should be impacted by this since the router always produces the correct form.

Doing so is ambiguous because you may also be trying to link to an element on the page whose id matches <div id="foo"> and it also erroneously will create an extra history state if a user clicks on something that transitions to that route again, since it will change location.hash === '#/foo'.

This ability will be removed quickly to allow us to mimic the browser's behavior of scrolling the page to an element whose id matches, but in our case doing so after the transition ends and everything is rendered. Once this feature is added, you'll be able to link to id's even with doubled up hashes: #/foo#some-id as well as the expected #some-id.

Deprecations Added in 1.9

§ More Consistent Handlebars Scope

In Ember 1.9, the each and with helpers come in two flavors: a "context-switching" flavor and a "named-parameter" flavor.

{{#each post in posts}}
  {{!-- the context in here is the same as the outside context,
        and `post` references the current iteration --}}
{{/each}}

{{#each posts}}
  {{!-- the context in here has shifted to the individual post.
        the outer context is no longer accessible --}}
{{/each}}

{{#with post as otherPost}}
  {{!-- the context in here is the same as the outside context }}
{{/with}}

{{#with post}}
  {{!-- the context in here has shifted to the post.
        the outer context is no longer accessible --}}
{{/with}}

This has proven to be one of the more confusing parts of the Ember templating system. It is also not clear to beginners which to use, and when they choose the context-shifting form, they lose access to values in the outer context that may be important.

Because the helper itself offers no clue about the context-shifting behavior, it is easy (even for more experienced Ember developers) to get confused when skimming a template about which object a value refers to.

The context-shifting forms of #each and #with have been deprecated in favor of the named-parameter forms. In Ember 1.12, the in and as syntax are further deprecated in favor of block params syntax. See the deprecation notes for in and deprecation notes for as.

Transition Plan

To transition your code to the new syntax, you can change templates that look like this:

{{#each people}}
  <p>{{firstName}} {{lastName}}</p>
  <p>{{address}}</p>
{{/each}}

with:

{{#each person in people}}
  <p>{{person.firstName}} {{person.lastName}}</p>
  <p>{{person.address}}</p>
{{/each}}

In preparation for further work on HTMLBars, the context switching form of {{each}} is deprecated. This is mostly a "mechanical" refactor and dramatically simplifies how to think about the context in your templates. This change should be entirely mechanical.

In prior versions you may have done one of the following:

<ul>
  {{#each}}
    <li>{{name}}</li>
  {{/each}}
</ul>
<ul>
  {{#each people}}
    <li>{{name}}</li>
  {{/each}}
</ul>

You should now be using:

<ul>
  {{#each person in this}}
    <li>{{person.name}}</li>
  {{/each}}
</ul>
<ul>
  {{#each person in people}}
    <li>{{person.name}}</li>
  {{/each}}
</ul>

Deprecations Added in 1.11

§ Binding Style Attributes

Content in Handlebars templates is automatically HTML-escaped to help developers prevent inadvertently introducing cross-site scripting (XSS) vulnerabilities into their applications. If you want to display trusted content as HTML, you can use a SafeString, a special string that tells Ember that the content should be displayed without escaping.

While this works great for HTML, there are some cases where you may bind user data that the browser interprets as CSS, not HTML. For example, you may bind the style attribute of an element:

<div style={{myStyle}}></div>

Handlebars only escapes HTML, not CSS, so this may introduce a potential XSS vulnerability into your application if a malicious user is able to provide data used in the myStyle property.

Starting in Ember 1.11, you will receive a warning if you attempt to bind the style attribute of an element. Once you have verified that the content being displayed is trusted and properly escaped, you can disable the warning by making the content a SafeString. For example:

  myStyle: Ember.computed('color', function() {
    /* Note: You must implement #escapeCSS. */
    var color = escapeCSS(this.get('color'));
    return Ember.String.htmlSafe("color: " + color);
  })

Make sure you don't put quotes around the sanitized string, myStyle, when you bound it in the template. This would prevent Ember from seeing it as safe.

You can learn more about SafeStrings and writing code that prevents XSS attacks by reading the Writing Helpers guide.

§ Access to Instances in Initializers

Previously, initializers had access to an object that allowed them to both register new classes and get instances of those classes.

If you have an initializer that gets instances of a class, you need to change it to use an instance initializer.

Change code that looks like this:

App.initializer({
  name: "clock",

  initialize: function(container, application) {
    application.register("clock:main", Clock);
    var clock = container.lookup("clock:main");
    clock.setStartTime(Date.now());
  }
});

To:

App.initializer({
  name: "clock",

  initialize: function(registry, application) {
    application.register("clock:main", Clock);
  }
});

App.instanceInitializer({
  name: "clock",

  initialize: function(instance) {
    var clock = instance.container.lookup("clock:main");
    clock.setStartTime(Date.now());
  }
});

Added in PR #10256.

For more information, see the instance initializer deprecation guide.

§ ObjectController

Experienced Ember users have enjoyed the use of proxying behavior in the Ember.ObjectController class since 1.0. However, this behavior will be removed in Ember 2.0 as the framework migrates to routable components.

New users hit three road bumps when learning about the object controller pattern.

  • Given a certain model, which of the three controller options should I be using?
  • Which controller is generated by the framework if I do not specify one?
  • When using an object controller, why should the this context not be passed to actions if it has the properties of my model?

For these reasons, the Road to Ember 2.0 RFC listed object controllers as a concept to be removed from the framework.

To migrate from an explicitly defined object controller, first convert the class definition to inherit from Ember.Controller. For example:

import Ember from "ember";

// Change:
export default Ember.ObjectController.extend({
// To:
export default Ember.Controller.extend({

// ...

Next update any use of {{modelPropertyName}} in templates with {{model.modelPropertyName}}. You should also review any computed property dependent keys, observer keys, and get and set statements on the route and controller. An example of how to make this migration can be found in this PR to the Ghost project.

If a controller is not explicitly defined, but instead is being auto-generated by the framework, it will only throw a deprecation message if the proxying behavior is being used.

Added in PR #10062.

Deprecations Added in 1.12

§ as syntax for {{with}}

Renaming a value using {{with}} has been possible using the as syntax. Block params, introduces in Ember 1.10, obsolete the as syntax.

With helpers should be updated to use block params. For example this helper:

{{#with foo as bar}}

Can be converted as follows:

{{#with foo as |bar|}}

§ in syntax for {{each}}

The in syntax is used to name an iterated value with {{each}}. Block params, introduced Ember 1.10, obsoletes the in syntax.

Each helpers should be updated to use block params. For example this helper:

{{#each foo in bar}}

Can be converted as follows:

{{#each bar as |foo|}}

For "itemController" :

{{#each foo in bar itemController="abc"}}

Can be converted as follows:

{{#each bar itemController="abc" as |foo|}}

§ Computed Properties With a Shared Getter And Setter

Ember.js 1.12 introduces an improved syntax for computed properties with a setter. Previously, computed properties with a setter implemented that setter by inspecting the number of arguments passed to the computed property's descriptor.

For example, this computed property splits a full name into two parts when set:

  fullName: Ember.computed("firstName", "lastName", function(key, newName) {
    if (arguments.length > 1) {
      var parts = newName.split(" ");
      this.setProperties({ firstName: parts[0], lastName: parts[1] });
      return newName;
    } else {
      return this.get("firstName") + " " + this.get("lastName");
    }
  });

These uses should be converted to use the new discrete getter and setter syntax introduced in 1.12:

  fullName: Ember.computed("firstName", "lastName", {
    get: function() {
      return this.get("firstName") + " " + this.get("lastName");
    },
    set: function(key, newName) {
      var parts = newName.split(" ");
      this.setProperties({ firstName: parts[0], lastName: parts[1] });
      return newName;
    }
  });

For further reading, review the RFC describing this feature and the pull request of the initial implementation.

Deprecations Added in 1.13

§ ArrayController

Just like Ember.ObjectController, Ember.ArrayController will be removed in Ember 2.0 for the same reasons mentioned in 1.11's ObjectController deprecation.

To migrate from an explicitly defined array controller, first convert the class definition to inherit from Ember.Controller.

Before:

import Ember from "ember";

export default Ember.ArrayController.extend({
});

After:

import Ember from "ember";

export default Ember.Controller.extend({
});

Next update any use of {{modelPropertyName}} in templates with {{model.modelPropertyName}}. You should also review any computed property dependent keys, observer keys, and get and set statements on the route and controller. An example of how to make this migration can be found in this PR to the Ghost project.

Opposite to 1.11's ObjectController deprecation, if a controller is not explicitly defined, but instead is being auto-generated by the framework, it will not throw a deprecation message even if the proxying behavior is being used.

Added in PR #11476.

§ beforeObserver

Before observers are deprecated due to the negative performance implications they have for Ember internals and applications.

Typically they were used to have access to the old value of a property when it's about to change, but you can get same functionality in an even more efficient way with just a few lines of code:

function fooObserver(obj){
  var newFoo = obj.get('foo');
  if (obj._oldFoo !== newFoo) {
    // do your stuff here
    obj._oldFoo = newFoo;
  }
}
addObserver(obj, 'foo', fooObserver);
fooObserver(obj); // Optionally call the observer immediately

The related functions Ember.addBeforeObserver, Ember.removeBeforeObserver, Ember.beforeObserversFor, and Function#beforeObserver are deprecated too.

§ bind-attr

Ember 1.11 introduced new syntax for using dynamic content in attributes.

In 1.10 your only option was to use the bind-attr helper:

<div {{bind-attr title=post.popup}}></div>

Ember 1.11 made it possible to intuitively represent dynamic content in attributes as you would expect:

<div title="{{post.popup}}"></div>

This makes it possible to express a number of concepts directly in the template that previously were awkward to represent and required computer properties, and could even require itemController.

Dasherized boolean values

Ember 1.11's attribute binding syntax no longer supports dasherizing for boolean values. For example:

app/components/user-profile.js
export default Ember.Component.extend({
  isActiveUser: true
});
app/templates/components/user-profile.hbs
<div {{bind-attr class="isActiveUser"}}>
</div>

Should be replaced with:

app/templates/components/user-profile.hbs
<div class="{{if isActiveUser 'is-active-user'}}">
</div>

Legacy bind-attr

Ember 1.13 deprecated bind-attr in favor of the new syntax.

To aid in the migration, you can use the legacy-bind-attr plugin to restore this behavior in Ember 2.0 and silence the deprecation warning.

§ Block and multi-argument unbound helper

The unbound helper is mostly a legacy API in Ember, although it is not being removed in 2.0. It was almost exclusively used to manage performance issues which are well addressed in Ember 1.13's new rendering engine.

The first form being deprecated is the block form of unbound. For example:

{{#unbound if someState}}
  Show this
{{/unbound}}

Instead, replace this form with a nested helper:

{{#if (unbound someState)}}
  Show this
{{/if}}

The second form being deprecated is the non-nested helper lookup. For example:

{{unbound some-custom-helper withArg anotherArg}}

Replace this form with nested helper call:

{{unbound (some-custom-helper withArg anotherArg)}}

Or make all inputs to the helper unbound:

{{some-custom-helper (unbound withArg) (unbound anotherArg)}}

§ Controller.needs

needs for controllers will be removed in Ember 2.0. You can migrate away from this using Ember.inject.controller.

Lets say you have a post controller like this:

import Ember from 'ember';

export default Ember.Controller.extend({
  needs: ['comments'],
  newComments: Ember.computed.alias('controllers.comments.newest')
});

You can upgrade to Ember.inject.controller like this:

import Ember from 'ember';

export default Ember.Controller.extend({
  comments: Ember.inject.controller(),
  newComments: Ember.computed.alias('comments.newest')
});

You can read more about Ember.inject.controller in the Ember API documentation.

§ Copyable.frozenCopy

Just as the Freezable mixin is deprecated in favor of functionality in core JavaScript, the frozenCopy method of the Copyable mixin is also deprecated in favor of Object.freeze().

Replace the following code:

const Obj = Ember.Object.extend(Freezable, Copyable, {
  copy() {
    return Obj.create();
  }
});

const frozenCopy = Obj.create().frozenCopy();
frozenCopy.isFrozen(); // => true
frozenCopy.set('foo', 'baz'); // => throws TypeError

with:

const a = Ember.Object.create();
Object.isFrozen(a); // => false
Object.freeze(a);
Object.isFrozen(a); // => true
a.set('foo', 'baz'); // => throws TypeError

§ Ember.CollectionView

Ember.CollectionView is deprecated as a consequence of the deprecation of Ember.View.

Legacy support of Ember.CollectionView will be provided via the ember-legacy-views addon.

Migrating away from Ember.CollectionView

In most cases collection view can be replaced using the {{each}} helper in combination with the {{component}} helper.

To be able to programmatically decide which component to use for an item, you can use a Ember.Helper

app/helpers/animal-component.js
import Ember from 'ember';

export default Ember.Helper.extend({
  compute: function(params, hash) {
    var type = params[0];
    return type + '-component';
  }
});

Then if you have these two components:

app/templates/components/dog-component.hbs
{{animal.name}} is a dog!
app/templates/components/cat-component.hbs
{{animal.name}} is a cat!

Then you can render your different animals like this:

app/controllers/animals.js
import Ember from 'ember';

export default Ember.Controller.extend({
  animals: [
    { type: 'cat', name: 'Buttons' },
    { type: 'dog', name: 'Fido'}
  ]
});
app/templates/animals.hbs
<ul>
{{#each animals as |animal|}}
  <li>
    {{component (animal-component animal.type) animal=animal}}
  </li>
{{else}}
  You have no animals.
{{/each}}
</ul>

You can view and manipulate this example on jsbin.

§ Ember.computed.any

Ember.computed.any is deprecated in favor of Ember.computed.or. This change is required because the computed value is the first value ( like || ) rather than a boolean value ( like Array.prototype.any ). For example:

let Hamster = Ember.Object.extend({
  hasClothes: Ember.computed.any('hat', 'shirt')
});

The above code will be changed to

let Hamster = Ember.Object.extend({
  hasClothes: Ember.computed.or('hat', 'shirt')
});

§ Ember.ContainerView

Ember.ContainerView is deprecated as a consequence of the deprecation of Ember.View.

Legacy support of Ember.ContainerView will be provided via the ember-legacy-views addon.

Migrating away from Ember.ContainerView

In most cases container view can be replaced using the {{each}} helper in combination with the {{component}} helper.

app/components/component-container.js
import Ember from 'ember';

export default Ember.Component.extend({
  classNames: ['the-container'],
  childComponents: ['a-component', 'b-component']
})
app/templates/components/component-container.hbs
{{#each childComponents as |childComponent|}}
  {{component childComponent}}
{{/each}}

§ Ember.create

Ember.create is deprecated in favor for Object.create. For more information regarding Object.create, please read the MDN documentation.

Please change this:

const doug = Ember.create({
  firstName: 'Doug'
});

to

const doug = Object.create({
  firstName: 'Doug'
});

§ Ember CreateWithMixins

Ember.Object.createWithMixins method has been deprecated. Instead call Ember.Object.create or Ember.Object.extend.

var obj = Ember.Object.createWithMixins({

});

Replace with code above with:

var obj = Ember.Object.extend({

}).create();

§ Ember.EnumerableUtils

EnumerableUtils has been deprecated in favor of native implementations. You can consult the JavaScript Array documentation to find suitable replacements for EnumerableUtils functionality. If the native JavaScript array object does not provide a suitable equivalent for your needs, you might also want to checkout third party utility libraries like lodash, or underscore.

§ Ember Freezable

The Freezable Mixin has been deprecated because the spec has been added to the javascript core.

Contact = Ember.Object.extend(Ember.Freezable, {
  firstName: null,
  lastName: null,

  // swaps the names
  swapNames: function() {
    if (this.get('isFrozen')) throw Ember.FROZEN_ERROR;
    var tmp = this.get('firstName');
    this.set('firstName', this.get('lastName'));
    this.set('lastName', tmp);
    return this;
  }

});

c = Contact.create({ firstName: "John", lastName: "Doe" });
c.swapNames();  // returns c
c.freeze();
c.swapNames();  // EXCEPTION

Replace code above with this:

Contact = Ember.Object.extend({
  firstName: null,
  lastName: null,

  // swaps the names
  swapNames: function() {
    if (Object.isFrozen(this)) throw Ember.FROZEN_ERROR;
    var tmp = this.get('firstName');
    this.set('firstName', this.get('lastName'));
    this.set('lastName', tmp);
    return this;
  }

});

c = Contact.create({ firstName: "John", lastName: "Doe" });
c.swapNames();  // returns c
Object.freeze(c);
c.swapNames();  // EXCEPTION

§ Ember.immediateObserver

Ember.immediateObserver is deprecated in favor of Ember.observer. Please change all instances of Ember.immediateObserver from:

Ember.Object.extend({
  valueObserver: Ember.immediateObserver('value', function() {
  })
});

to

Ember.Object.extend({
  valueObserver: Ember.observer('value', function() {
  })
});

§ Ember.keys

Ember.keys is deprecated in favor for Object.keys. For more information regarding Object.keys, please read the MDN documentation.

const food = Ember.keys({
  yellow: 'banana',
  green: 'pickle'
});

to

const food = Object.keys({
  yellow: 'banana',
  green: 'pickle'
});

§ Ember.LinkView

As a consequence of the deprecation of Ember.View, many internal views have been ported to component. Ember.LinkView, the class that backs the {{link-to}} helper, have been ported to Ember.LinkComponent.

Most uses of Ember.LinkView can be directly ported to the Ember.LinkComponent class.

§ Ember.oneWay

Ember.oneWay is deprecated in favor for Ember.computed.oneWay. Please change all instances of Ember.oneWay from:

let User = Ember.Object.extend({
  firstName: null,
  nickName: Ember.oneWay('firstName')
});

to

let User = Ember.Object.extend({
  firstName: null,
  nickName: Ember.computed.oneWay('firstName')
});

§ Ember.ReduceComputedProperty / Ember.ArrayComputedProperty

In addition to Ember.ReduceComputed and Ember.ArrayComputed you were able to add a property function call, making use of the ReduceComputedProperty and ArrayComputedProperty classes. This usage is also deprecated. For an example, consider a counter component that displays a total sum of values in an array. Using Ember.ReduceComputedProperty would show the following:

totalCount: Ember.reduceComputed({
  initialValue = 0;
  addedItem(totalValue, newValue) {
    return totalValue + newValue;
  },
  removedItem(totalValue, newValue) {
    return totalValue - newValue;
  }
}).property('values')

Now that these APIs are deprecated, use the native JavaScript reduce along with the Ember.computed family of functions:

totalCount: Ember.computed('values.[]', function() {
  return this.get('values').reduce((previousValue, currentValue) => {
    return previousValue + currentValue;
  })
})

§ Ember.Select

Using the Ember.Select global and its view helper form ({{view 'select'}}) is deprecated. Ember.Select in Ember 1.x is implemented in a legacy coding style that uses patterns such as observers and two-way data binding that can cause pathological performance problems and provide an experience that is not consistent with idiomatic Ember 2.0 development.

Legacy support of Ember.Select will be provided via the ember-legacy-views addon.

Ember 2.0 provides several new features that make it much more straightforward to implement <select> tag functionality via the data-down, actions-up paradigm in one's codebase. For example, to create a component that displays a prompt and a list of dropdown options, the following code could be used:

app/templates/components/my-select.hbs
<select {{action 'change' on='change'}}>
  {{#each content key="@index" as |item|}}
    <option value="{{item}}" selected={{is-equal item selectedValue}}>
      {{item}}
    </option>
  {{/each}}
</select>
app/components/my-select.js
import Ember from "ember";

export default Ember.Component.extend({
  content: null,
  selectedValue: null,

  didInitAttrs(attrs) {
    this._super(...arguments);
    var content = this.get('content');

    if (!content) {
      this.set('content', []);
    }
  },

  actions: {
    change() {
      const changeAction = this.get('action');
      const selectedEl = this.$('select')[0];
      const selectedIndex = selectedEl.selectedIndex;
      const content = this.get('content');
      const selectedValue = content[selectedIndex];

      this.set('selectedValue', selectedValue);
      changeAction(selectedValue);
    }
  }
});
app/helpers/is-equal.js
// is-equal helper is necessary to determine which option is currently selected.
import Ember from "ember";

export default Ember.Helper.helper(function([leftSide, rightSide]) {
  return leftSide === rightSide;
});

This component could be used in a template by supplying it an array of strings as content and an action to call when the user makes a selection as change:

app/templates/application.hbs
The currently selected item: {{mySelectedItem}}.

{{! myItems is an array of strings: ['first', 'second', 'third',...] }}
{{! This uses the `action` and `mut` helpers to pass in an action that
    will update the (outer scope) `mySelectedItem` property with the user's
    selection. }}
{{my-select content=myItems action=(action (mut mySelectedItem))}}

A more complete example of a select component that mimics many features of the deprecated Ember.Select is available in this jsbin.

Here is an example jsbin showing usage of the select tag directly in a template without a component.

There are many Ember-CLI addons that provide select-like functionality. emberx-select in particular aims to provide a select component based on the native html select. Alternative select component implementations can be iterated upon in addons to identify best practices and perhaps moved into an official Ember 2.x implementation in the future.

§ Ember.View

Ember 1.x encouraged a Model-View-Controller-Route architecture. Since then, the web has consolidated around a Model-Component-Route pattern for web development that Ember 2.0 also embraces.

Views are removed from the Ember 2.0 API. However a single release is likely insufficient for large apps to upgrade their entire codebase away from routable views, and consequently Ember is providing extended support for views via the ember-legacy-views addon. This addon will remain compatible with Ember until v2.4 of the framework is released.

Along with the deprecation of Ember.View, the Ember.CoreView class is also deprecated. If you were using this class, please migrate to Ember.Component.

Migrating away from the view helper

In most cases Ember views can be replaced with a component. For example this view and usage:

app/templates/show-menu.hbs
{{view.title}}
app/views/show-menu.js
import Ember from "ember";

// Usage: {{view "show-menu"}}
export default Ember.View.extend({
  templateName: 'some-menu',
  title: 'My Menu'
});

Can be replaced with this component:

app/templates/components/show-menu.hbs
{{title}}
app/components/show-menu.js
import Ember from "ember";

// Usage: {{show-menu}}
export default Ember.Component.extend({
  title: 'My Menu'
});

Note that a component is always its own context in a template. A view's template may have had access to other variables that were present where it was called, such as a controller. A component template will always be isolated, and if data is needed it should be passed upon invocation. For example:

{{show-menu options=controller.menuOptions}}

Differences in yielded blocks

Some notable differences exist between yielded view blocks and yielded component blocks. A view could be used this way:

app/views/reverse-name.js
import Ember from "ember";

export default Ember.View.extend({
  reversedName: Ember.computed('name', function() {
    return this.get('name').split("").reverse().join("");
  })
});
{{#view "reverse-name" name=controller.name}}
  {{view.reversedName}}
{{/view}}

Components introduced block params. This concept achieves data passing without the confusion over what {{view}} refers to. For example:

app/components/reverse-name.js
import Ember from "ember";

export default Ember.Component.extend({
  reversedName: Ember.computed('name', function() {
    return this.get('name').split("").reverse().join("");
  })
});
app/templates/components/reverse-name.hbs
{{yield reversedName}}
{{#reverse-name name=controller.name as |reversedName|}}
  {{reversedName}}
{{/reverse-name}}

Just as passing values to a component allow access to those values in the isolated template of that component, yielding block params allow for values from the component to be passed to the location the component was called at.

Routable Views

When a template for a given route is rendered, if there is a view with the same name that view will also be used. For example this view is attached to the rendered route template:

app/router.js
import Ember from "ember";

export default Ember.Router.map(function() {
  this.route('about');
});
app/views/about.js
import Ember from "ember";

export default Ember.View.extend({
  classNameBindings: ['controller.isActive:active']
});

There are only two reasons a view may be used for a route.

  • To set attribute bindings
  • To attach event handlers

You should migrate away from routed views. For example to attach bindings an element in the template is sufficient:

app/router.js
import Ember from "ember";

export default Ember.Router.map(function() {
  this.route('about');
});
app/templates/about.hbs
<div class="{{if isActive 'active'}}">
  <!-- something something -->
</div>

If attaching events or sharing DOM is necessary, consider a component:

app/router.js
import Ember from "ember";

export default Ember.Router.map(function() {
  this.route('about');
});
app/templates/about.hbs
{{#active-layout isActive=isActive}}
  <!-- something something -->
{{/active-layout}}
app/components/active-layout.js
import Ember from "ember";

export default Ember.Component.extend({
  classNameBindings: ['isActive:active'],
  click() {
    // Handle click
  }
});

view and controller template keywords

View and controller keywords provided a way to access the view or controller backing a template, even across scopes that you might expect to be isolated. In many ways their behavior is emergent, and generally is poorly designed.

Accessing data via {{view.someProp}} or {{controller.thisThing}} can nearly always be replaced by proper use of data passing and block params. See the guide on differences in yielded blocks for a complete example of using block params over the {{view}} keyword.

view and viewClass arguments to {{outlet}}

Since the View API is deprecated starting in 1.13, providing the view or viewClass argument to the {{outlet}} helper is likewise deprecated in favor of migrating to component-based approaches, as explained earlier in this section.

§ Handlebars / HTMLBars helpers

All the various ways to create helpers on the Handlebars and HTMLBars namespace have been deprecated in favor of the Ember.Helper class and it's Ember.Helper.helper function. The makeViewHelper method has been deprecated in favor of just using an Ember.Component.

makeViewHelper

If you use viewHelper you should refactor the view class and template into a component with the same name as the view helper you registered.

makeBoundHelper, registerBoundHelper, helper, registerHelper

If you have for example:

app/helpers/foo-bar.js
export default Ember.HTMLBars.makeBoundHelper(function(firstArg, secondArg, options) {
  let { hash } = options;
  // helper code
});

or

Ember.Handlebars.registerHelper('foo-bar', function(firstArg, secondArg, options) {
  let { hash } = options;
  // helper code
});

you can replace those with:

app/helpers/foo-bar.js
export default Ember.Helper.helper(function([firstArg, secondArg], hash) {
  // helper code
});

§ Modifying a Property within didInsertElement

Because of changes in the way Ember renders components in 1.13, setting properties within an overridden didInsertElement method will result in a deprecation warning.

In many cases you can move your logic earlier in the component lifecycle by implementing the didReceiveAttrs hook, one of the new hooks introduced in 1.13.

  didReceiveAttrs() {
    this._super(...arguments);
    this.set('myValue', value);
  }

It may even be possible, if your value is constant, to move the change to init, where the value can only be set once.

  init() {
    this._super(...arguments);
    this.set('myValue', myValue);
  }

Don't see a set in your didInsertElement? It may be in didUpdate or didRender. Due to a bug, these report the wrong method name.

Still can't find it?

  • One of your computed properties may be calling set.
  • An observer is being triggered.
  • One of your child components may be setting one of its attributes and it is being propagated upward to this component.

In rare cases, you may be measuring the DOM rendered and want to set an attribute based on that. In this case, it is ok to cause a second render:

  didInsertElement() {
    this._super(...arguments);
    run.schedule('afterRender', this, this.measureAndSet);
  },

  measureAndSet() {
    this.set('height', this.element.children.length * 30);
  }

However, doing so is relatively slow and should be done as a last resort.

§ Non-Standard Ways of Calling Ember.set and Ember.get

Ember is deprecating calls to get and set made in non-standard ways. Below are examples of the calls that are now deprecated:

// referencing properties with globals
Ember.set(null, 'App.foo', bar);
Ember.set('App.foo', bar);
Ember.get(null, 'App.foo');
Ember.get('App.foo');

// referencing properties with 'this'
Ember.set(obj, 'this.foo', bar);
Ember.get(obj, 'this.foo');

// providing a null context
Ember.set(null, 'foo', bar);
Ember.get(null, 'foo');

// providing a non-string property
Ember.set(obj, 42, bar);
Ember.get(obj, 42);

§ Overriding render When Extending a Component or View

The render method on components and views should not be overridden and will go away in Ember 2.x. Modifications to Ember rendering should be made by overriding Ember's new component lifecycle hooks , introduced in version 1.13.

§ RenderBuffer

With the change to the rendering process in Glimmer, the RenderBuffer is no longer used by Ember and will be removed. The class was originally private, and any reference to it should be removed.

§ Reversed Ember.observer Arguments

A little known feature of the observer function allowed for the arguments to be listed in the opposite order, with function first:

Ember.observer(function() {
  // React to observer firing here
}, 'property1', 'property2')

This syntax was deprecated in Ember 1.13.5. The above code sample should be replaced with the standard syntax that lists the observed properties, then the function as the last argument:

Ember.observer('property1', 'property2', function() {
  // React to observer firing here
})

§ Set positionalParams as a static property on the class

Setting positionalParams within .extend is deprecated. It has to be set as a static property on the class itself (.reopenClass).

Please change this:

import Ember from 'ember';

export default Ember.Component.extend({
  positionalParams: [ 'a', 'b' ]
});

to this:

import Ember from 'ember';

var Thing = Ember.Component.extend();
Thing.reopenClass({
  positionalParams: [ 'a', 'b' ]
});

§ SortableMixin

Along with Ember.ArrayController, Ember.SortableMixin will be removed in Ember 2.0.

You can migrate away from using Ember.SortableMixin by using Ember.computed.sort. Using this example:

const SongsController = Ember.ArrayController.extend(Ember.SortableMixin, {
  model: null,
  sortProperties: ['trackNumber'],
  sortAscending: false
});

let songsController = SongsController.create({ songs: songList });

You can transition to using Ember.computed.sort like this:

const SongsController = Ember.Controller.extend({
  model: null,
  sortedSongs: Ember.computed.sort('model', 'songSorting'),
  songSorting: ['trackNumber:desc']
});

let songsController = SongsController.create({ songs: songList });

You can read more about Ember.computed.sort in the Ember API documentation

Legacy support of Ember.SortableMixin will be provided via the ember-legacy-controllers addon.

§ Using / for namespace in the {{render}} helper

When using the render helper, its possible to specify a context within a nested directory structure. Prior to 1.13, it was acceptable to separate nested directories using slashes /, but this is now deprecated, as Ember has settled on using dot notation for namespacing.

For example, a developer might have a controller at app/controllers/blog/posts.js and is using the render helper to render that controller context within another template.

Instead of using {{render 'blog/posts'}}, use {{render 'blog.posts'}}. That will render the template associated with app/controllers/blog/posts.js in your directory structure.

§ Using @each as a leaf node in a dependent key

Using @each at the end of a computed key is deprecated and will not work in Ember 2.0

invalid: Ember.computed('myObject.@each', function () {
  //no longer valid for monitoring changes to arrays
});

When defining dependent keys for computed properties, ember 2.0+ will treat @each and [] differently.

@each will monitor specific properties within an array of objects.

eachProp: Ember.computed('myObj.posts.@each.title', function () {
  //fired whenever one of the blog post titles is changed.
});

[] will monitor mutations to the array.

arrProp: Ember.computed('myObj.posts.[]', function () {
  //fired whenever a blog post is added or removed
});

§ Using @guid and @item as key in {{each}}

As of 1.13.2 you should not use key='@guid' or key='@item' in the {{each}} helper. It is documented in 1.13 that you can use these special values to uniquely identify each array entry so that the view code could more efficiently re-render the display of the list.

This process was simplified as part of a bugfix, where the key now implicitly defaults to @identity, which will pick either a guid or an item identifier based on type of each array entry. Therefore, providing the key attribute is no longer required for re-render optimizations.

Applications should replace instances of:

{{#each people key='@guid' as |person|}}
...
{{/each}}

and

{{#each people key='@item' as |person|}}
...
{{/each}}

with

{{#each people as |person|}}
...
{{/each}}

§ Using this.get('template')

Prior to 1.13, developers had to check for the existance of the internal template property to determine the existance of a yielded block. This is being deprecated in favor of using the new hasBlock property within your templates.

Determining if a block has been provided from JavaScript

Currently the hasBlock property is not made available from the JavaScript component object, so there is no clean alternative for checking whether a block has been passed. See the JavaScript hasBlock RFC for discussion on making this information available in the future, as well as possible workarounds.

§ Using TrackedArray or SubArray

TrackedArray and SubArray were internal classes used as implementation details of ArrayComputed. ArrayComputed is deprecated to be removed in version 2.0.0. As a result, these 2 private classes will be removed as well.

§ Using the {{with}} Helper with the controller option

Another option deprecated due to the de-emphasis of controllers is the controller option used with the {{with}} helper. In prior versions it was possible to specify a controller when using {{with}}, which would use an instance of the specified controller as the context within the block.

{{#with item.posts controller="myposts"}}
  {{!- numPosts fetched from the controller instance "myposts" }}
  There are {{numPosts}} posts.
{{with}

Similar to the deprecated {{each}} helper controller options , this approach triggers less performant compatibility code and is deprecated in favor of using local properties or components.

Using local properties

{{#with item.posts as |myPosts|}}
  There are {{myPosts.length}} posts.
{{with}}

Using a component

{{! prints the number of posts (if available) }}
{{post-status posts=item.posts}}

§ View and Controller options on the {{each}} helper

The options itemView, itemViewClass, tagName, emptyView, emptyViewClass and itemController on the {{each}} helper have been deprecated.

These options were added when controllers were more prominent, and the component story was less fully fleshed out. In Ember 1.13+, using these options triggers a less performant compatibility mode.

The usage of all the above mentioned options should be replaced with components.

An example

{{each view.comments itemController="comment"
                     itemView="commentView"
                     emptyView="noCommentsView"
                     tagName="ul"}}

Can be replaced with:

<ul>
  {{#each comments as |comment|}}
    {{post-comment comment=comment}}
  {{else}}
    {{no-comments}}
  {{/each}}
</ul>

Breaking the example down

The comment controller and commentView view have been refactored into the {{post-comment}} component.

The noCommentsView has been refactored in to the {{no-comments}} component.

Instead of the itemController="comment" and itemView="commentView" we use the newly created {{post-comment}} component.

Instead of the emptyView="noCommentsView" we use the {{no-comments}} component.

We replaced tagName="ul" part by just surrounding the {{each}} block with <ul> tags.