Deprecation Guide for Ember.Evented and @ember/object/events
The Ember.Evented
mixin, the underlying @ember/object/events
module (addListener
, removeListener
, sendEvent
), and the on()
function from @ember/object/evented
are all deprecated.
These APIs provided a way for Ember objects to send and receive events. With modern JavaScript features and patterns, we recommend more explicit and standard approaches. For eventing, we recommend refactoring to use a library like emittery.
Please note: The methods from Evented
(on
, one
, off
, trigger
, has
) were also available on Ember.Component
, Ember.Route
, and Ember.Router
. While usage on these objects is deprecated, the methods will continue to be supported and not deprecated on the RouterService
, since key parts of its functionality are difficult to reproduce without them.
Replacing Evented
with emittery
We recommend the emittery library, which is a modern, promise-based event emitter. This is useful for services that need to broadcast state changes across an application, such as a session service that announces login and logout events.
First, add emittery
to your project:
npm install --save-dev emittery
# or
pnpm add --save-dev emittery
Here is an example of a session service that used Evented
:
Before
// app/services/session.js
import Service from '@ember/service';
import Evented from '@ember/object/evented';
import { tracked } from '@glimmer/tracking';
export default class SessionService extends Service.extend(Evented) {
@tracked user = null;
login(userData) {
this.user = userData;
this.trigger('loggedIn', userData);
}
logout() {
const oldUser = this.user;
this.user = null;
this.trigger('loggedOut', oldUser);
}
}
A consumer might use it like this:
// app/components/some-component.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { registerDestructor } from '@ember/destroyable';
export default class SomeComponent extends Component {
@service session;
constructor(owner, args) {
super(owner, args);
this.session.on('loggedIn', this, 'handleLogin');
registerDestructor(this, () => {
this.session.off('loggedIn', this, 'handleLogin');
});
}
handleLogin(user) {
console.log(`User logged in: ${user.name}`);
// ... update component state
}
}
After refactoring to use emittery
, the service manages its own event emitter and provides clear methods for subscribing.
After
// app/services/session.js
import Service from '@ember/service';
import { tracked } from '@glimmer/tracking';
import Emittery from 'emittery';
export default class SessionService extends Service {
@tracked user = null;
#emitter = new Emittery();
login(userData) {
this.user = userData;
this.#emitter.emit('loggedIn', userData);
}
logout() {
const oldUser = this.user;
this.user = null;
this.#emitter.emit('loggedOut', oldUser);
}
// Public subscription methods
onLoggedIn(callback) {
return this.#emitter.on('loggedIn', callback);
}
onLoggedOut(callback) {
return this.#emitter.on('loggedOut', callback);
}
}
The listening object can then use registerDestructor
from @ember/destroyable
to tie the subscription's lifetime to its own. This removes the need for a willDestroy
hook and manual cleanup.
// app/components/some-component.js
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { registerDestructor } from '@ember/destroyable';
export default class SomeComponent extends Component {
@service session;
constructor(owner, args) {
super(owner, args);
const unsubscribe = this.session.onLoggedIn((user) => {
this.handleLogin(user);
});
registerDestructor(this, unsubscribe);
}
handleLogin(user) {
console.log(`User logged in: ${user.name}`);
// ... update component state
}
}