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 or 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
sendActionyou 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 it 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' }
Deprecate calling `A` as a constructor
until: 3.9.0
id: array.new-array-wrapper
The A function imported from @ember/array is a function that can be used
to apply array mixins to an existing object (generally a native array):
import { A } from '@ember/array';
let arr = [];
A(arr);
arr.pushObject(1);
A will also return the "wrapped" array for convenience, and if no array is
passed will create the array instead:
let arr1 = A([]);
let arr2 = A();
Because A is a standard function, it can also be used as a constructor. The
constructor does not actually do anything different (because Javascript
constructors can return something other than an instance). This was not intended
behavior - A was originally implemented as an arrow function which cannot be
used as a constructor, but as a side effect of transpilation it was turned into
a normal function which could.
To update, remove any usage of new with A, and call A as a standard
function:
// before
let arr = new A();
// after
let arr = A();
If linting rules prevent you from doing this, rename A to indicate that it is
a function and not a constructor:
import { A as emberA } from '@ember/array';
let arr = emberA();
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:
ComponentControllerServiceRouteModel
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');
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: {
sendAnalytics() {
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 Router usages of willTransition and didTransition.
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: {
sendAnalytics() {
let routeInfo = this.router.currentRoute;
ga.send('pageView', {
pageName: routeInfo.name,
metaData: {
queryParams: routeInfo.queryParams,
params: routeInfo.params,
}
});
}
}
});
Deprecations Added in 3.8
Component Manager Factory Function
until: 4.0.0
id: component-manager-string-lookup
setComponentManager no longer takes a string to associate the custom component class and the component manager. Instead you must pass a factory function that produces an instance of the component manager.
Before:
import { setComponentManager } from '@ember/component';
import BasicComponent from './component-class';
setComponentManager('basic', BasicComponent);
After:
import { setComponentManager } from '@ember/component';
import BasicComponent from './component-class';
import BasicManager from './component-manager';
setComponentManager(owner => {
return new BasicManager(owner)
}, BasicComponent);
Deprecations Added in 3.9
Application controller router properties
until: 4.0.0
id: application-controller.router-properties
If you are reliant on the currentPath and currentRouteName properties of the ApplicationController, you can get the same functionality from the Router service.
To migrate, inject the Router service and read the currentRouteName off of it.
Before:
import Controller from '@ember/controller';
import fetch from 'fetch';
export default Controller.extend({
store: service('store'),
actions: {
sendPayload() {
fetch('/endpoint', {
method: 'POST',
body: JSON.stringify({
route: this.currentRouteName
})
});
}
}
})
After:
import Controller from '@ember/controller';
import fetch from 'fetch';
export default Controller.extend({
store: service('store'),
router: service('router'),
actions: {
sendPayload() {
fetch('/endpoint', {
method: 'POST',
body: JSON.stringify({
route: this.router.currentRouteName
})
});
}
}
})
Computed Property Overridability
until: 4.0.0
id: computed-property.override
Ember's computed properties are overridable by default if no setter is defined:
const Person = EmberObject.extend({
firstName: 'Diana',
lastName: 'Prince',
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
});
let person = Person.create();
person.fullName; // Diana Prince
person.set('fullName', 'Carol Danvers');
person.set('firstName', 'Bruce');
person.set('lastName', 'Wayne');
person.fullName; // Carol Danvers
This behavior is bug prone and has been deprecated. readOnly(), the modifier
that prevents this behavior, will be deprecated once overridability has been
removed.
If you still need this behavior, you can create a setter which accomplishes this manually:
const Person = EmberObject.extend({
firstName: 'Diana',
lastName: 'Prince',
fullName: computed('firstName', 'lastName', {
get() {
if (this._fullName) {
return this._fullName;
}
return `${this.firstName} ${this.lastName}`;
},
set(key, value) {
return this._fullName = value;
}
})
});
Computed Property `.property()` Modifier
until: 4.0.0
id: computed-property.property
.property() is a modifier that adds additional property dependencies to an
existing computed property:
const Person = EmberObject.extend({
fullName: computed(function() {
return `${this.firstName} ${this.lastName}`;
}).property('firstName', 'lastName')
});
To update, move the dependencies to the main computed property definition:
const Person = EmberObject.extend({
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
});
In the case of the filter, map, and sort computed property macros, it was
previously possible to need to add dependencies because they weren't available
in the public APIs of those macros. An optional second parameter has now been
added to these macros which is an array of additional dependent keys, allowing
you to pass these dependencies to them:
// before
const Person = EmberObject.extend({
friendNames: map('friends', function(friend) {
return friend[this.get('nameKey')];
}).property('nameKey')
});
// after
const Person = EmberObject.extend({
friendNames: map('friends', ['nameKey'], function(friend) {
return friend[this.get('nameKey')];
})
});
Custom computed property macros that encounter this issue should also be refactored to be able to receive the additional keys as parameters.
Computed Property Volatility
until: 4.0.0
id: computed-property.volatile
NOTE: There is a bug in Native Getters in 3.9 that was fixed in 3.10. To upgrade to 3.9 directly, just add this to your deprecation workflow, and make the recommended fixes when you move to 3.10 or beyond.
.volatile() is a computed property modifier which makes a computed property
recalculate every time it is accessed, instead of caching. It also prevents
property notifications from ever occuring on the property, which is generally
not the behavior that developers are after. Volatile properties are usually used
to simulate the behavior of native getters, which means that they would
otherwise behave like normal properties.
To update, use native getters directly instead:
Before:
const Person = EmberObject.extend({
fullName: computed(function() {
return `${this.firstName} ${this.lastName}`;
}).volatile()
});
After:
const Person = EmberObject.extend({
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
});
Deprecate `@ember/object#aliasMethod`
until: 4.0.0
id: object.alias-method
@ember/object#aliasMethod is a little known and rarely used method that allows
user's to add aliases to objects defined with EmberObject:
import EmberObject, { aliasMethod } from '@ember/object';
export default EmberObject.extend({
foo: 123,
bar() {
console.log(this.foo);
},
baz: aliasMethod('bar'),
});
This can be refactored into having one function call the other directly:
import EmberObject from '@ember/object';
export default EmberObject.extend({
foo: 123,
bar() {
console.log(this.foo);
},
baz() {
this.bar(...arguments);
},
});
Avoid defining methods directly on the class definition, since this will not translate well into native class syntax in the future:
// Do not use this, this is an antipattern! 🛑
import EmberObject from '@ember/object';
function logFoo() {
console.log(this.foo);
}
export default EmberObject.extend({
foo: 123,
bar: logFoo,
baz: logFoo,
});
Replace jQuery APIs
until: 4.0.0
id: jquery-apis
As of Ember 3.4.0, Ember no longer requires that all applications include jQuery, therefore APIs that are coupled to jQuery have been deprecated.
Since jQuery is not needed by Ember itself anymore, and many apps (e.g. mobile apps) are
sensitive about performance, it is often beneficial for those to
avoid shipping jQuery. If this is not a major concern for your app, and you see value in using jQuery, it is
absolutely fine to continue doing so. It is just not included by default anymore, so you have to opt in to
using it with the @ember/jquery package as described below.
For addons it is a bit different as they are not aware of the context in which they are used. Any addon that still relies on jQuery will either force their consuming apps to continue bundling jQuery, or will not be usable for apps that decide not to do so. Therefore it is highly recommended to avoid relying on jQuery in general, unless there is a good reason (e.g. an addon wrapping a jQuery plugin).
Added deprecations
The main jQuery integration API that has been deprecated is this.$() inside of an Ember.Component, which would give you a
jQuery object of the component's element. Instead, you can use the this.element property, which provides a
reference to a native DOM element:
import Component from '@ember/component';
export default Component.extend({
waitForAnimation() {
this.$().on('transitionend', () => this.doSomething());
}
});
should be changed to:
import Component from '@ember/component';
export default Component.extend({
waitForAnimation() {
this.element.addEventListener('transitionend', () => this.doSomething());
}
});
If you used this.$() to query for child elements, you can do so as well with native DOM APIs:
import Component from '@ember/component';
export default Component.extend({
waitForAnimation() {
this.$('.animated').on('transitionend', () => this.doSomething());
}
});
should be changed to:
import Component from '@ember/component';
export default Component.extend({
waitForAnimation() {
this.element.querySelectorAll('.animated')
.forEach((el) => el.addEventListener('transitionend', () => this.doSomething()));
}
});
This applies in a similar fashion to component tests using the setupRenderingTest() helper. Instead of using
this.$() in a test, you should use this.element (or alternatively the find()/findAll() helpers from
@ember/test-helpers):
test('it disables the button', async function(assert) {
// ...
assert.ok(this.$('button').prop('disabled'), 'Button is disabled');
});
should be changed to:
test('it disables the button', async function(assert) {
// ...
assert.ok(this.element.querySelector('button').disabled, 'Button is disabled');
});
If you do continue to use jQuery, please make sure to always import it like this:
import jQuery from 'jquery';
Accessing it from the Ember namespace as Ember.$ is and will remain deprecated.
Opting into jQuery
Apps and addons which require jQuery, can opt into the jQuery integration now provided by
the @ember/jquery package. This will provide the this.$() API to Ember.Components, and
will make sure that the EventDispatcher will provide jQuery events to a component's event handler methods to
maintain compatibility. this.$() deprecation warnings will still be displayed.
ember install @ember/jquery
ember install @ember/optional-features
ember feature:enable jquery-integration
For addons make sure that @ember/jquery is added as a dependency in its package.json!
Deprecations Added in 3.11
Function.prototype.observes
until: 4.0.0
id: function-prototype-extensions.observes
Historically, Ember has extended the Function.prototype with a few functions
(on, observes, property), over time we have moved away from using these
prototype extended functions in favor of using the official ES modules based
API.
In order to migrate away from Function.prototype.observes you would update to using
observer from @ember/object (see
documentation)
directly.
For example, you would migrate from:
import EmberObject from '@ember/object';
export default EmberObject.extend({
valueObserver: function() {
// Executes whenever the "value" property changes
}.observes('value')
});
Into:
import EmberObject, { observer } from '@ember/object';
export default EmberObject.extend({
valueObserver: observer('value', function() {
// Executes whenever the "value" property changes
})
});
Please review the deprecation RFC over at emberjs/rfcs for more details.
Function.prototype.on
until: 4.0.0
id: function-prototype-extensions.on
Historically, Ember has extended the Function.prototype with a few functions
(on, observes, property), over time we have moved away from using these
prototype extended functions in favor of using the official ES modules based
API.
In order to migrate away from Function.prototype.on you would update to using
@ember/object/evented (see
documentation)
directly.
For example, you would migrate from:
import EmberObject from '@ember/object';
import { sendEvent } from '@ember/object/events';
let Job = EmberObject.extend({
logCompleted: function() {
console.log('Job completed!');
}.on('completed')
});
let job = Job.create();
sendEvent(job, 'completed'); // Logs 'Job completed!'
Into:
import EmberObject from '@ember/object';
import { on } from '@ember/object/evented';
import { sendEvent } from '@ember/object/events';
let Job = EmberObject.extend({
logCompleted: on('completed', function() {
console.log('Job completed!');
})
});
let job = Job.create();
sendEvent(job, 'completed'); // Logs 'Job completed!'
Please review the deprecation RFC over at emberjs/rfcs for more details.
Function.prototype.property
until: 4.0.0
id: function-prototype-extensions.property
Historically, Ember has extended the Function.prototype with a few functions
(on, observes, property), over time we have moved away from using these
prototype extended functions in favor of using the official ES modules based
API.
In order to migrate away from Function.prototype.property you would update to using
computed from @ember/object (see
documentation)
directly.
For example, you would migrate from:
import EmberObject from '@ember/object';
let Person = EmberObject.extend({
init() {
this._super(...arguments);
this.firstName = 'Betty';
this.lastName = 'Jones';
},
fullName: function() {
return `${this.firstName} ${this.lastName}`;
}.property('firstName', 'lastName')
});
let client = Person.create();
client.get('fullName'); // 'Betty Jones'
client.set('lastName', 'Fuller');
client.get('fullName'); // 'Betty Fuller'
Into:
import EmberObject, { computed } from '@ember/object';
let Person = EmberObject.extend({
init() {
this._super(...arguments);
this.firstName = 'Betty';
this.lastName = 'Jones';
},
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
});
let client = Person.create();
client.get('fullName'); // 'Betty Jones'
client.set('lastName', 'Fuller');
client.get('fullName'); // 'Betty Fuller'
Please review the deprecation RFC over at emberjs/rfcs for more details.
Deprecations Added in 3.13
Deprecate mouseEnter/Leave/Move events in {{action}} modifier
until: 4.0.0
id: action.mouseenter-leave-move
As mouseenter, mouseleave and mousemove events fire very frequently, are rarely used and have a higher
implementation cost, support for them in Ember's EventDispatcher has been deprecated. As such these events should
not be used with the {{action}} modifier anymore.
Before:
<button {{action "handleMouseEnter" on="mouseEnter"}}>Hover</button>
After:
<button {{on "mouseenter" this.handleMouseEnter}}>Hover</button>
Deprecate mouseEnter/Leave/Move component methods
until: 4.0.0
id: component.mouseenter-leave-move
As mouseenter, mouseleave and mousemove events fire very frequently, are rarely used and have a higher
implementation cost, support for them in Ember's EventDispatcher has been deprecated. As such the corresponding
event handler methods in Ember.Component should not be used anymore.
Before:
import Component from '@ember/component';
export default class MyComponent extends Component {
mouseEnter(e) {
// do something
}
}
After:
import Component from '@ember/component';
import { action } from '@ember/object';
export default class MyComponent extends Component {
@action
handleMouseEnter(e) {
// do something
}
didInsertElement() {
super.didInsertElement(...arguments);
this.element.addEventListener('mouseenter', this.handleMouseEnter);
}
willDestroyElement() {
super.willDestroyElement(...arguments);
this.element.removeEventListener('mouseenter', this.handleMouseEnter);
}
}
An alternative to attaching the event listener in the component class is to opt into outer HTML semantics by making the
component tag-less and using the {{on}} modifier in the template:
import Component from '@ember/component';
import { action } from '@ember/object';
export default class MyComponent extends Component {
tagName = '';
@action
handleMouseEnter(e) {
// do something
}
}
<div {{on "mouseenter" this.handleMouseEnter}}>
...
</div>
Deprecations Added in 3.15
Deprecate `Component#isVisible`
until: 4.0.0
id: ember-component.is-visible
We are deprecating usage of the isVisible in classic components in accordance with RFC #324.
Instead of setting the isVisible property on classic components, consider either using a wrapping {{#if}} or the hidden attribute:
{{! wrapping `if` }}
{{#if this.showComponent}}
<MyComponent />
{{/if}}
{{! `hidden` attribute }}
<div hidden={{this.isHidden}}></div>
Deprecate `{{partial}}`
until: 4.0.0
id: ember.partial
We are deprecating usage of {{partial}} in accordance with RFC #449.
Partials should be migrated to components. For example, consider the following quick-tip partial:
{{! app/templates/application.hbs }}
{{#let (hash title="Don't use partials" body="Components are always better") as |tip|}}
{{partial "partials/quick-tip"}}
{{/let}}
{{! app/templates/partials/quick-tip.hbs }}
<h1>Tip: {{tip.title}}</h1>
<p>{{tip.body}}</p>
<button {{on "click" this.dismissTip}}>OK</button>
It can be converted to a component as follows:
{{! app/templates/application.hbs }}
{{#let (hash title="Don't use partials" body="Components are always better") as |tip|}}
<QuickTip @tip={{tip}} @onDismiss={{this.dismissTip}} />
{{/let}}
{{! app/templates/components/quick-tip.hbs }}
<h1>Tip: {{@tip.title}}</h1>
<p>{{@tip.body}}</p>
<button {{action @onDismiss}}>OK</button>
Deprecations Added in 3.16
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.
Deprecations Added in Upcoming Features
Prototype Function Listeners
until: 3.9.0
id: events.inherited-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);
}
}
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.