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 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:

  • 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');

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 without deprecation warnings, and will make sure that the EventDispatcher will provide jQuery events to a component's event handler methods to maintain compatibility.

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}`;
  }.computed('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 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.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.