Deprecations Added in Ember 3.x

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

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

Deprecations Added in 3.1

Getting the @each property

until: 3.5.0
id: getting-the-each-property

Calling array.get('@each') is deprecated. @each may only be used as dependency key.

Use notifyPropertyChange instead of propertyWillChange and propertyDidChange

until: 3.5.0
id: use-notifypropertychange-instead-of-propertywillchange-and-propertydidchange

Ember.Application#registry / Ember.ApplicationInstance#registry

The private APIs propertyWillChange and propertyDidChange will be removed after the first LTS of the 3.x cycle. You should remove any calls to propertyWillChange and replace any calls to propertyDidChange with notifyPropertyChange. This applies to both the Ember global version and the EmberObject method version.

For example, the following:

Ember.propertyWillChange(object, 'someProperty');
doStuff(object);
Ember.propertyDidChange(object, 'someProperty');

object.propertyWillChange('someProperty');
doStuff(object);
object.propertyDidChange('someProperty');

Should be changed to:

doStuff(object);
Ember.notifyPropertyChange(object, 'someProperty');

doStuff(object);
object.notifyPropertyChange('someProperty');

If you are an addon author and need to support both Ember applications greater than 3.1 and less than 3.1 you can use the polyfill ember-notify-property-change-polyfill

Deprecations Added in 3.2

Use console rather than Ember.Logger

until: 4.0.0
id: ember-console.deprecate-logger

Use of Ember.Logger is deprecated. You should replace any calls to Ember.Logger with calls to console.

In Edge and IE11, uses of console beyond calling its methods may require more subtle changes than simply substituting console wherever Logger appears. In these browsers, they will behave just as they do in other browsers when your development tools window is open. However, when run normally, calls to its methods must not be bound to anything other than the console object or you will receive an Invalid calling object exception. This is a known inconsistency with these browsers. (Edge issue #14495220.)

To avoid this, transform the following:

var print = Logger.log; // assigning method to variable

to:

// assigning method bound to console to variable
var print = console.log.bind(console);

Also, transform any of the following:

Logger.info.apply(undefined, arguments); // or
Logger.info.apply(null, arguments); // or
Logger.info.apply(this, arguments); // or

to:

console.info.apply(console, arguments);

Finally, because node versions before version 9 don't support console.debug, you may want to transform the following:

Logger.debug(message);

to:

if (console.debug) {
  console.debug(message);
} else {
  console.log(message);
}

Add-on Authors

If your add-on needs to support both Ember 2.x and Ember 3.x clients, you will need to test for the existence of console before calling its methods. If you do much logging, you may find it convenient to define your own wrapper. Writing the wrapper as a service will provide for dependency injection by tests and perhaps even clients.

Private property `Route.router` has been renamed to `Route._router`

until: 3.5.0
id: ember-routing.route-router

The Route#router private API has been renamed to Route#_router to avoid collisions with user-defined properties or methods. If you want access to the router, you are probably better served injecting the router service into the route like this:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
  router: service()
});

Use defineProperty to define computed properties

until: 3.5.0
id: ember-meta.descriptor-on-object

Although uncommon, it is possible to assign computed properties directly to objects and have them be implicitly computed from eg Ember.get. As part of supporting ES5 getter computed properties, assigning computed properties directly is deprecated. You should replace these assignments with calls to defineProperty.

For example, the following:

let object = {};
object.key = Ember.computed(() => 'value');
Ember.get(object, 'key') === 'value';

Should be changed to:

let object = {};
Ember.defineProperty(object, 'key', Ember.computed(() => 'value'));
Ember.get(object, 'key') === 'value';

Deprecations Added in 3.3

Use ember-copy addon instead of copy method and Copyable mixin.

until: 4.0.0
id: ember-runtime.deprecate-copy-copyable

Since Ember's earliest days, the copy function and Copyable mixin from @ember/object/internals were intended to be treated as an Ember internal mechanism. The Copyable mixin, in particular, has always been marked private, and it is required in order to use copy with any Ember Object-derived class without receiving an assertion.

Copyable hasn't been used by any code inside of Ember for a very long time, except for the NativeArray mixin, inherited by Ember arrays. The deprecated copy function now handles array copies directly, no longer delegating to NativeArray.copy. With this deprecation, NativeArray no longer inherits from Copyable and its implementation of copy is also deprecated.

For shallow copies of data where you use copy(x) or copy(x, false), the ES6 Object.assign({}, x) will provide the desired effect. For deep copies, copy(x, true), the most efficient and concise approach varies with the situation, but several options are available in open source.

For those whose code is deeply dependent upon the existing implementation, copy and Copyable have been extracted to the ember-copy addon . If you are only using documented methods, this will only require adjusting your import statements to use the methods from ember-copy instead of @ember/object/internals. The code in the addon should work identically to what you were using before.

Use native events instead of jQuery.Event

until: 4.0.0
id: jquery-event

As part of the effort to decouple Ember from jQuery, using event object APIs that are specific to jQuery.Event such as originalEvent are deprecated. Especially addons are urged to not use any jQuery specific APIs, so they are able to work in a world without jQuery.

Using native events

jQuery events copy most of the properties of their native event counterpart, but not all of them. See the jQuery.Event API for further details. These properties will work with jQuery events as well as native events, so just use them without originalEvent.

Before:

// your event handler:
click(event) {
  let x = event.originalEvent.clientX;
  ...
}

After:

// your event handler:
click(event) {
  let x = event.clientX;
  ...
}

For those other properties it was necessary to get access to the native event object through originalEvent though. To prevent your code from being coupled to jQuery, use the normalizeEvent function provided by ember-jquery-legacy, which will work with our without jQuery to provide the native event without triggering any deprecations.

ember install ember-jquery-legacy

Before:

// your event handler:
click(event) {
  let nativeEvent = event.originalEvent;
  ...
}

After:

import { normalizeEvent } from 'ember-jquery-legacy';

// your event handler:
click(event) {
  let nativeEvent = normalizeEvent(event);
  ...
}

Opting into jQuery

For apps which are ok to work only with jQuery, you can explicitly opt into the jQuery integration and thus quash the deprecations:

ember install @ember/jquery
ember install @ember/optional-features
ember feature:enable jquery-integration

Deprecations Added in 3.4

Use closure actions instead of `sendAction`

until: 4.0.0
id: ember-component.send-action

In Ember 1.13 closure actions were introduced as a recommended replacement for sendAction. With sendAction the developer passes the name of an action, and when sendAction is called Ember.js would look up that action in the parent context and invoke it if present. This had a handful of caveats:

  • Since the action is not looked up until it's about to be invoked, it's easier for a typo in the action's name to go undetected.

  • Using sendAction you cannot receive the return value of the invoked action.

Closure actions solve those problems and on top are also more intuitive to use.

// controllers/index.js
export default Controller.extend({
  actions: {
    sendData(data) {
      fetch('/endpoint', { body: JSON.stringify(data) });
    }
  }
})
{{!-- templates/index.hbs --}}
{{my-component submit="sendData"}}
// my-component.js
this.sendAction('submit');

Should be changed to:

// controllers/index.js
export default Controller.extend({
  actions: {
    sendData(data) {
      fetch('/endpoint', { body: JSON.stringify(data) });
    }
  }
})
{{!-- templates/index.hbs --}}
{{my-component submit=(action "sendData")}}
// my-component.js
export default Component.extend({
  click() {
    this.submit();
  }
});

Note that with this approach the component MUST receive that submit property, while with sendAction if it didn't it would silently do nothing.

If you don't want submit to be mandatory, you have to check for the presence of the action before calling it:

export default Component.extend({
  click() {
    if (this.submit) {
      this.submit();
    }
  }
});

Another alternative is to define an empty action on the component, which helps clarify that the function is not mandatory:

// my-component.js
export default Component.extend({
  submit: () => {},
  //...
  click() {
    this.submit();
  }
});

This deprecation also affects the built-in {{input}} helper that used to allow passing actions as strings:

{{input enter="handleEnter"}}

Since this uses sendAction underneath is also deprecated and must also be replaced by closure actions:

{{input enter=(action "handleEnter")}}

Deprecations Added in 3.6

Ember.merge

until: 4.0.0
id: ember-polyfills.deprecate-merge

Ember.merge predates Ember.assign, but since Ember.assign has been released, Ember.merge has been mostly unnecessary. To cut down on duplication, we are now recommending using Ember.assign instead of Ember.merge. If you need to support Ember <= 2.4 you can use ember-assign-polyfill to make Ember.assign available to you.

Before:

import { merge } from '@ember/polyfills';

var a = { first: 'Yehuda' };
var b = { last: 'Katz' };
merge(a, b); // a == { first: 'Yehuda', last: 'Katz' }, b == { last: 'Katz' }

After:

import { assign } from '@ember/polyfills';

var a = { first: 'Yehuda' };
var b = { last: 'Katz' };
assign(a, b); // a == { first: 'Yehuda', last: 'Katz' }, b == { last: 'Katz' }

HandlerInfos Removal

until: 3.9.0
id: remove-handler-infos

HandlerInfo was a private API that has been renamed to RouteInfo to align with the router service RFC. If you need access to information about the routes, you are probably better served injecting the router service as it exposes a publically supported version of the RouteInfos. You can access them in the following ways:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
  router: service(),
  init() {
    this._super(...arguments);
    this.router.on('routeWillChange', transition => {
      let { to: toRouteInfo, from: fromRouteInfo } = transition;
      console.log(`Transitioning from -> ${fromRouteInfo.name}`);
      console.log(`to -> ${toRouteInfo.name}`);
    });

    this.router.on('routeDidChange', transition => {
      let { to: toRouteInfo, from: fromRouteInfo } = transition;
      console.log(`Transitioned from -> ${fromRouteInfo.name}`);
      console.log(`to -> ${toRouteInfo.name}`);
    });
  }

  actions: {
    sendAnaltics() {
      let routeInfo = this.router.currentRoute;
      ga.send('pageView', {
        pageName: routeInfo.name,
        metaData: {
          queryParams: routeInfo.queryParams,
          params: routeInfo.params,
        }
      });
    }
  }
});

Deprecate Router Events

until: 4.0.0
id: deprecate-router-events

Application-wide transition monitoring events belong on the Router service, not spread throughout the Route classes. That is the reason for the existing willTransition and didTransition hooks/events on the Router. But they are not sufficient to capture all the detail people need.

In addition, they receive handlerInfos in their arguments, which are an undocumented internal implementation detail of router.js that doesn't belong in Ember's public API. Everything you can do with handlerInfos can be done with the RouteInfo.

Below is how you would transition both the Route and Router usages of willTransition and didTransition.

Route

From:

import Route from '@ember/routing/route';

export default Route.extend({
  actions: {
    willTransition(transition) {
      if (this.controller.get('userHasEnteredData') &&
          !confirm('Are you sure you want to abandon progress?')) {
        transition.abort();
      } else {
        // Bubble the `willTransition` action so that
        // parent routes can decide whether or not to abort.
        return true;
      }
    },

    didTransition() {
      this.controller.get('errors.base').clear();
      return true; // Bubble the didTransition event
    }
  }
});

To:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
  router: service(),
  init() {
    this._super(...arguments);
    this.router.on('routeWillChange', transition => {
      if (this.controller.get('userHasEnteredData') &&
          !confirm('Are you sure you want to abandon progress?')) {
        transition.abort();
      }

      // No need to return, no longer in a bubbling API
    });

    this.router.on('routeDidChange', transition => {
      this.controller.get('errors.base').clear();
      // No need to return, no longer in a bubbling API
    });
  }

Router

From:

import Router from '@ember/routing/router';
import { inject as service } from '@ember/service';

export default Router.extend({
  currentUser: service('current-user'),

  willTransition(transition) {
    this._super(...arguments);
    if (!this.currentUser.isLoggedIn) {
      transition.abort();
      this.transitionTo('login');
    }
  },

  didTransition(privateInfos) {
    this._super(...arguments);
    ga.send('pageView', {
      pageName: privateInfos.name
    });
  }
});

To:

import Router from '@ember/routing/router';
import { inject as service } from '@ember/service';

export default Router.extend({
  currentUser: service('current-user'),

  init() {
    this._super(...arguments);
    this.on('routeWillChange', transition => {
      if (!this.currentUser.isLoggedIn) {
        transition.abort();
        this.transitionTo('login');
      }
    });

    this.on('routeDidChange', transition => {
      ga.send('pageView', {
        pageName: transition.to.name
      });
    });
  }
});

Transition State Removal

until: 3.9.0
id: transition-state

The Transition object is a public interface that actually exposed internal state used by router.js to perform routing. Accessing state, queryParams or params on the Transition has been removed. If you need access to information about the routes, you are probably better served injecting the router service as it exposes a publically supported version of the RouteInfos. You can access them in the following ways:

import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';

export default Route.extend({
  router: service(),
  init() {
    this._super(...arguments);
    this.router.on('routeWillChange', transition => {
      let { to: toRouteInfo, from: fromRouteInfo } = transition;
      console.log(`Transitioning from -> ${fromRouteInfo.name}`);
      console.log(`From QPs: ${JSON.stringify(fromRouteInfo.queryParams)}`);
      console.log(`From Params: ${JSON.stringify(fromRouteInfo.params)}`);
      console.log(`From ParamNames: ${fromRouteInfo.paramNames.join(', ')}`);
      console.log(`to -> ${toRouteInfo.name}`);
      console.log(`To QPs: ${JSON.stringify(toRouteInfo.queryParams)}`);
      console.log(`To Params: ${JSON.stringify(toRouteInfo.params)}`);
      console.log(`To ParamNames: ${toRouteInfo.paramNames.join(', ')}`);
    });

    this.router.on('routeDidChange', transition => {
      let { to: toRouteInfo, from: fromRouteInfo } = transition;
      console.log(`Transitioned from -> ${fromRouteInfo.name}`);
      console.log(`From QPs: ${JSON.stringify(fromRouteInfo.queryParams)}`);
      console.log(`From Params: ${JSON.stringify(fromRouteInfo.params)}`);
      console.log(`From ParamNames: ${fromRouteInfo.paramNames.join(', ')}`);
      console.log(`to -> ${toRouteInfo.name}`);
      console.log(`To QPs: ${JSON.stringify(toRouteInfo.queryParams)}`);
      console.log(`To Params: ${JSON.stringify(toRouteInfo.params)}`);
      console.log(`To ParamNames: ${toRouteInfo.paramNames.join(', ')}`);
    });
  }

  actions: {
    sendAnaltics() {
      let routeInfo = this.router.currentRoute;
      ga.send('pageView', {
        pageName: routeInfo.name,
        metaData: {
          queryParams: routeInfo.queryParams,
          params: routeInfo.params,
        }
      });
    }
  }
});

Deprecations Added in Upcoming Features

Use ember-cli resolver rather than legacy globals resolver

until: 4.0.0
id: ember.deprecate-globals-resolver

Over the past years we have transitioned to using Ember-CLI as the main way to compile Ember apps. The globals resolver is a holdover and primarily facilitates use of Ember without Ember-CLI.

If at all possible, it is highly recommended that you transition to using ember-cli to build your Ember applications. Most of the community already uses it and it provides many benefits including a rich addon ecosystem.

However, if you do have a custom build system, or are using Ember App Kit, you can adapt your current build tools and configuration instead of using ember-cli if you really need to.

Instead of extending from Ember.DefaultResolver or @ember/globals-resolver, extend from the ember-cli-resolver.

Then throughout your app, instead of compiling to:

App.NameOfThingTypeOfThing,

transpile to named amd strict syntax with module name of

<app-name/type-of-things/name-of-things>

which looks like this after transpilation

// import bar from 'bar';
// export default foo(bar);
define("my-app/utils/foo", ["exports", "bar"], function (exports, bar) {
  "use strict";

  exports.__esModule = true;

  exports["default"] = foo(bar);
});

Also, instead of including your templates in index.html, precompile your templates using the precompiler that is included with the version of Ember.js you intend to use it with. This can be found in the ember-source package under dist/ember-template-compiler.js.

Additionally, instead of using the Ember.TEMPLATES array to lookup a template, you can import it in your code:

import layout from './template.js';

export default Ember.Component.extend({ layout });

Finally, instead of creating a global namespace

App.Utils = Ember.Namespace.create();

simply create a directory and when transpiling, include the directory name in your module name.

define('my-app/utils/...', /*...*/);

If you need additional help transitioning your globals build system, feel free to reach out to someone on the Ember Community Discord or the Discourse forum.

Prototype Function Listeners

until: 3.9.0
id: events.prototype-function-listeners

Currently, function listeners and string listeners behave identically to each other. Their inheritance and removal structure is the same, and they can be used interchangeably for the most part. However, function listeners can be much more expensive as they maintain a reference to the function.

Function listeners also have limited utility outside of per instance usage. Consider the following example which the same listener using strings and using function references:

class Foo {
  method() {}
}

addListener(Foo, 'event', null, 'method');
addListener(Foo, 'event', null, Foo.prototype.method);

It's clear that the string version is much more succinct and preferable. A more common alternative would be adding the listener to the instance in the constructor:

class Foo {
  constructor() {
    addListener(this, 'event', this, this.method);
  }

  method() {}
}

But in this case, the listener doesn't need to be applied to the prototype either.

Updating

In cases where function listeners have been added to a prototype, and those functions do exist on the prototype, replace them with string listeners:

Before:

class Foo {
  method() {}
}

addListener(Foo, 'event', null, Foo.prototype.method);

After:

class Foo {
  method() {}
}

addListener(Foo, 'event', null, 'method');

In cases where function listeners have been added to a prototype for arbitrary functions which do not exist on the prototype, you can convert the function to a method, create a wrapper function, or add the listener on the instance instead:

Before:

function foo() {}

class Foo {}

addListener(Foo, 'event', null, foo);

After:

class Foo {
  foo() {}
}

addListener(Foo, 'event', null, 'foo');

// OR
function originalFoo() {}

class Foo {
  foo() {
    originalFoo();
  }
}

addListener(Foo, 'event', null, 'foo');

// OR
function foo() {}

class Foo {
  constructor() {
    addListener(this, 'event', this, foo);
  }
}

new EmberObject

until: 3.9.0
id: object.new-constructor

We are deprecating usage of new EmberObject() to construct instances of EmberObject and it's subclasses. This affects all classes that extend from EmberObject as well, including user defined classes and Ember classes such as:

  • Component
  • Controller
  • Service
  • Route
  • Model

Instead, you should use EmberObject.create() to create new instances of classes that extend from EmberObject. If you are using native class syntax instead of EmberObject.extend() to define your classes, you can also refactor to not extend from EmberObject, and continue to use new syntax.

Refactoring to use create() instead of new

Before this deprecation, new EmberObject() and EmberObject.create() were functionally the same, with one difference - new EmberObject() could only receive 1 argument, whereas EmberObject.create() could receive several. Because new was strictly less powerful, you can safely refactor existing code to call create with the same arguments as before:

Before:

let obj1 = new EmberObject();
let obj2 = new EmberObject({ prop: 'value' });

const Foo = EmberObject.extend();
let foo = new Foo({ bar: 123 });

After:

let obj1 = EmberObject.create();
let obj2 = EmberObject.create({ prop: 'value' });

const Foo = EmberObject.extend();
let foo = Foo.create({ bar: 123 })

Refactoring native classes to not extend from EmberObject

If you are using native class syntax to extend from EmberObject, you can instead define your classes without a base class. This means that you will have to write your own constructor function:

Before:

class Person extends EmberObject {}

let rwjblue = new Person({ firstName: 'Rob', lastName: 'Jackson' });

After:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
}

let rwjblue = new Person('Rob', 'Jackson');

This is closer to the way native classes are meant to work, and can help with low level performance concerns such as shaping. It also enforces clear interfaces which can help define the purpose of a class more transparently.

Remove All Listeners/Observers

until: 3.9.0
id: events.remove-all-listeners

When using both the removeListener and removeObserver methods, users can omit the final string or method argument to trigger an undocumented codepath that will remove all event listeners/observers for the given key:

let foo = {
  method1() {}
  method2() {}
};

addListener(foo, 'init', 'method1');
addListener(foo, 'init', 'method2');

removeListener(foo, 'init');

This functionality will be removed since it is uncommonly used, undocumented, and adds a fair amount of complexity to a critical path. To update, users should remove each listener individually:

let foo = {
  method1() {}
  method2() {}
};

addListener(foo, 'init', 'method1');
addListener(foo, 'init', 'method2');

removeListener(foo, 'init', 'method1');
removeListener(foo, 'init', 'method2');

Ember.String namespace

until: 4.0.0
id: ember-string.namespace

We are deprecating usage of the utilities under the Ember.String namespace.

This includes:

  • camelize
  • capitalize
  • classify
  • dasherize
  • decamelize
  • underscore
  • loc
  • w
  • htmlSafe
  • isHTMLSafe

Starting with Ember v2.18.0 [TODO: update to proper version], the @ember/string package was extracted from the Ember codebase into a separate module. This marks the beginning of the effort to slim down the code that is included by default in Ember applications.

You should ember install @ember/string.

camelize, capitalize, classify, dasherize, decamelize, underscore, w

Go from:

import Ember from "ember";

Ember.String.camelize("my string"); // MyString
Ember.String.capitalize("my string"); // My String
Ember.String.classify("my string"); // MyString
Ember.String.dasherize("my string"); // my-string
Ember.String.decamelize("MyString"); // my string
Ember.String.underscore("my string"); // my_string

Ember.String.w("my string"); //

To:

import {
  camelize,
  capitalize,
  classify,
  dasherize,
  decamelize,
  underscore
} from "@ember/string";

camelize("my string"); // MyString
capitalize("my string"); // My String
classify("my string"); // MyString
dasherize("my string"); // my-string
decamelize("MyString"); // my string
underscore("my string"); // my_string

w("my string"); //
Ember.String.htmlSafe and Ember.String.isHTMLSafe

Go from:

import Ember from "ember";

let myString = "my string";

Ember.String.htmlSafe(myString);
Ember.String.isHTMLSafe(myString);

or:

import { htmlSafe, isHTMLSafe } from "@ember/component";

let myString = "my string";

htmlSafe(myString);
isHTMLSafe(myString);

To:

import { htmlSafe, isHTMLSafe } from "@ember/template";

let myString = "my string";

htmlSafe(myString);
isHTMLSafe(myString);
Ember.String.loc and import { loc } from '@ember/string'

We recommend using a proper localization/internationalization solution.

You can consult a curated list of addons that provide these functionalities in the Ember Observer Internationalization category.

Ember.String prototype extensions

until: 4.0.0
id: ember-string.prototype_extensions

a. "hello".capitalize() to capitalize("hello").

b. ember install ember-string-prototype-extensions.