Deprecations Added in Ember 6.x
What follows is a list of deprecations introduced to Ember during the 6.x cycle.
For more information on deprecations in Ember, see the main deprecations page.
Deprecations Added in 6.3.0
Importing inject
from @ember/service
Importing inject
from @ember/service
is deprecated. Please import service
instead.
Example:
import { Route } from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { service } from '@ember/service';
export default class MyRoute extends Route {
@service store;
}
You can use the ember-codemod-remove-inject-as-service codemod, to fix all violations.
If you're working on a library that needs to support ember-source prior to 4.1, you can support both styles of service
via:
import * as emberService from '@ember/service';
const service = emberService.service ?? emberService.inject;
Deprecations Added in 6.5.0
Deprecation of ArrayProxy
ArrayProxy
is deprecated. In modern Ember, you should use tracking primitives—such as tracked arrays and tracked properties—whenever possible. This is almost always the best approach going forward. Some of the examples below demonstrate alternatives using proxies or advanced patterns, but these are generally not ideal. You should strongly consider refactoring your code to use tracking if at all possible, as it leads to simpler, more maintainable, and idiomatic Ember code. However, the best replacement depends on how you were using ArrayProxy
. Some example use cases are shown below.
Recommended: Use Tracked Arrays
For most use cases, the modern Ember approach is to use tracked arrays from tracked-built-ins
:
import { TrackedArray } from 'tracked-built-ins';
// Instead of ArrayProxy, use TrackedArray directly
const pets = new TrackedArray(['dog', 'cat', 'fish']);
// The array is automatically tracked and will update templates
pets.push('bird');
pets[0]; // 'dog'
pets.length; // 4
This provides automatic tracking without the complexity of proxies and follows modern Ember patterns.
Advanced Use Cases
If you need more advanced behavior like content swapping or transformation, you can use the approaches below. However, these patterns are generally not recommended unless you have a strong reason not to use tracked arrays and properties. In most cases, refactoring to use tracking primitives will result in better, more future-proof code.
Swapping Content
If you were using ArrayProxy
to easily swap out the underlying array while keeping a stable reference, you can achieve a similar, transparent effect using a native Proxy
backed by a class with a @tracked
property. Again, prefer tracked arrays and properties if you can refactor your code to use them.
Before:
import ArrayProxy from '@ember/array/proxy';
import { A } from '@ember/array';
const pets = A(['dog', 'cat', 'fish']);
const proxy = ArrayProxy.create({ content: pets });
proxy.get('firstObject'); // 'dog'
// Later, you can easily swap the content
proxy.set('content', A(['amoeba', 'paramecium']));
proxy.get('firstObject'); // 'amoeba'
After:
import { tracked } from '@glimmer/tracking';
// A helper class to hold the tracked state.
class SwappableState {
@tracked content;
constructor(initialContent) {
this.content = initialContent;
}
}
// A factory function to create a proxy that is transparent
// and allows swapping the underlying content.
function createSwappableArray(initialContent) {
const state = new SwappableState(initialContent);
return new Proxy(state, {
get(target, property, receiver) {
// Allow getting/setting the content directly for swapping.
if (property === 'content') {
return target.content;
}
// Delegate all other property access to the content array.
return Reflect.get(target.content, property, receiver);
},
set(target, property, value, receiver) {
// Allow setting the content directly for swapping.
if (property === 'content') {
target.content = value;
return true;
}
// Delegate all other property sets to the content array.
return Reflect.set(target.content, property, value, receiver);
},
// Add other traps to make the proxy behave like a full array.
has: (target, key) => key in target.content,
ownKeys: (target) => Reflect.ownKeys(target.content),
getOwnPropertyDescriptor: (target, key) => Reflect.getOwnPropertyDescriptor(target.content, key),
defineProperty: (target, key, desc) => Reflect.defineProperty(target.content, key, desc),
deleteProperty: (target, key) => Reflect.deleteProperty(target.content, key),
});
}
const pets = createSwappableArray(['dog', 'cat', 'fish']);
// Access the array transparently using native syntax.
pets[0]; // 'dog'
pets.length; // 3
// Later, you can easily swap the content.
// Any part of your app observing this will update because
// the underlying state is tracked.
pets.content = ['amoeba', 'paramecium'];
pets[0]; // 'amoeba'
pets.length; // 2
Transforming Content
If you were using objectAtContent
to transform the array's content, you can use a native JavaScript Proxy
to achieve the same result with standard array syntax. This is an advanced pattern and should only be used if refactoring to tracked properties is not feasible.
Before:
import ArrayProxy from '@ember/array/proxy';
let pets = ['dog', 'cat', 'fish'];
let proxy = ArrayProxy.create({
content: pets,
objectAtContent(idx) {
return this.get('content').objectAt(idx).toUpperCase();
}
});
proxy.get('firstObject'); // 'DOG'
proxy.objectAt(1); // 'CAT'
After:
const pets = ['dog', 'cat', 'fish'];
const transformedPets = new Proxy(pets, {
get(target, property, receiver) {
// Check if the property is an array index.
if (typeof property === 'string' && /^\d+$/.test(property)) {
const index = parseInt(property, 10);
const value = target[index];
return typeof value === 'string' ? value.toUpperCase() : value;
}
// For other properties like 'length', delegate to the original array.
return Reflect.get(target, property, receiver);
}
});
// Now you can access the transformed items using native array syntax.
transformedPets[0]; // 'DOG'
transformedPets[1]; // 'CAT'
// Other array properties work as expected.
transformedPets.length; // 3
Sorted or Filtered Content (arrangedContent
)
If you were using arrangedContent
to provide a sorted or filtered view of an array, the modern approach is to use tracked properties and getters:
Before:
import ArrayProxy from '@ember/array/proxy';
import { computed } from '@ember/object';
import { A } from '@ember/array';
const people = A([{name: 'Yehuda'}, {name: 'Tom'}]);
const proxy = ArrayProxy.extend({
arrangedContent: computed('content.[]', function() {
// In classic Ember, `sortBy` was a common way to do this.
return this.get('content').sortBy('name');
})
}).create({ content: people });
proxy.get('arrangedContent.firstObject.name'); // 'Tom'
// Mutating the content...
people.pushObject({ name: 'Chris' });
// ...is reflected in arrangedContent.
proxy.get('arrangedContent.firstObject.name'); // 'Chris'
After (modern Ember approach with tracked properties):
import { TrackedArray } from 'tracked-built-ins';
import { cached } from '@glimmer/tracking';
class PeopleManager {
// Use TrackedArray for automatic reactivity
people = new TrackedArray([{name: 'Yehuda'}, {name: 'Tom'}]);
@cached
get arrangedContent() {
// Automatically recomputes when people array changes
return [...this.people].sort((a, b) => a.name.localeCompare(b.name));
}
}
const manager = new PeopleManager();
manager.arrangedContent[0].name; // 'Tom'
// Mutating the content...
manager.people.push({ name: 'Chris' });
// ...is reflected in arrangedContent due to @cached and TrackedArray.
manager.arrangedContent[0].name; // 'Chris'
For more complex use cases where you need a native Proxy
for dynamic behavior, you can use the following pattern. However, this is rarely necessary and should be avoided if you can use tracked properties and computed values instead:
// The original data, which can be mutated.
const people = [{name: 'Yehuda'}, {name: 'Tom'}];
// A cache for the sorted version.
let sortedCache = null;
let isDirty = true;
const peopleProxy = new Proxy(people, {
get(target, property, receiver) {
// Intercept access to a special 'arranged' property.
if (property === 'arranged') {
if (isDirty) {
// The cache is dirty, so we re-compute the sorted array.
sortedCache = [...target].sort((a, b) => a.name.localeCompare(b.name));
isDirty = false;
}
return sortedCache;
}
// For any other property, delegate to the original array.
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
// Any mutation to the array marks the cache as dirty.
isDirty = true;
return Reflect.set(target, property, value, receiver);
}
});
// Access the sorted content via the `arranged` property.
peopleProxy.arranged[0].name; // 'Tom'
// Mutate the original data through the proxy.
peopleProxy.push({ name: 'Chris' });
// The `arranged` property now reflects the change because the cache was invalidated.
peopleProxy.arranged[0].name; // 'Chris'
Migration Strategy
When migrating from ArrayProxy
, consider:
- First choice (strongly recommended): Use
TrackedArray
fromtracked-built-ins
and tracked properties for automatic reactivity and idiomatic Ember code. - For computed arrays: Use
@cached
getters with tracked data. - Only if truly necessary: Use native
Proxy
for complex dynamic behavior that cannot be achieved with tracked properties. This should be rare.
The modern Ember approach strongly favors explicit tracking and computed properties over proxy-based solutions. Tracking primitives are easier to understand, debug, and optimize, and will be the best choice for almost all use cases going forward.
Ember.__loader
Previously, __loader
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.__loader
There is no replacement for this API.
Ember._action
Previously, _action
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._action
If needed, _action
can be imported:
import { action } from '@ember/object';
Ember._array
Previously, _array
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._array
If needed, _array
can be imported:
import { array } from '@ember/helper';
Ember._assertDestroyablesDestroyed
Previously, _assertDestroyablesDestroyed
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._assertDestroyablesDestroyed
If needed, _assertDestroyablesDestroyed
can be imported:
import { assertDestroyablesDestroyed } from '@ember/destroyable';
Ember._associateDestroyableChild
Previously, _associateDestroyableChild
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._associateDestroyableChild
If needed, _associateDestroyableChild
can be imported:
import { associateDestroyableChild } from '@ember/destroyable';
Ember._Backburner
Previously, _Backburner
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._Backburner
_Backburner
is also private.
There is no replacement for this API.
Ember._Cache
Previously, _Cache
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._Cache
There is no replacement for this API.
Ember._cacheGetValue
Previously, _cacheGetValue
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._cacheGetValue
If needed, _cacheGetValue
can be imported:
import { getValue } from '@glimmer/tracking/primitives/cache';
Ember._cacheIsConst
Previously, _cacheIsConst
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._cacheIsConst
If needed, _cacheIsConst
can be imported:
import { isConst } from '@glimmer/tracking/primitives/cache';
Ember._captureRenderTree
Previously, _captureRenderTree
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._captureRenderTree
_captureRenderTree
is also private.
If needed, _captureRenderTree
can be imported:
import { captureRenderTree } from '@ember/debug';
However, due to _captureRenderTree
being private, it is not recommended, nor supported.
Ember._componentManagerCapabilities
Previously, _componentManagerCapabilities
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._componentManagerCapabilities
If needed, _componentManagerCapabilities
can be imported:
import { capabilities } from '@ember/component';
Ember._concat
Previously, _concat
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._concat
If needed, _concat
can be imported:
import { concat } from '@ember/helper';
Ember._ContainerProxyMixin
Previously, _ContainerProxyMixin
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._ContainerProxyMixin
_ContainerProxyMixin
is also private.
There is no replacement for this API.
Ember._createCache
Previously, _createCache
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._createCache
If needed, _createCache
can be imported:
import { createCache } from '@glimmer/tracking/primitives/cache';
Ember._dependentKeyCompat
Previously, _dependentKeyCompat
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._dependentKeyCompat
If needed, _dependentKeyCompat
can be imported:
import { dependentKeyCompat } from '@ember/object/compat';
Ember._descriptor
Previously, _descriptor
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._descriptor
There is no replacement for this API.
Ember._enableDestroyableTracking
Previously, _enableDestroyableTracking
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._enableDestroyableTracking
If needed, _enableDestroyableTracking
can be imported:
import { enableDestroyableTracking } from '@ember/destroyable';
Ember._fn
Previously, _fn
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._fn
If needed, _fn
can be imported:
import { fn } from '@ember/helper';
Ember._getComponentTemplate
Previously, _getComponentTemplate
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._getComponentTemplate
If needed, _getComponentTemplate
can be imported:
import { getComponentTemplate } from '@ember/component';
Ember._get
Previously, _get
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._get
If needed, _get
can be imported:
import { get } from '@ember/helper';
Ember._getPath
Previously, _getPath
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._getPath
There is no replacement for this API.
Ember._hash
Previously, _hash
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._hash
If needed, _hash
can be imported:
import { hash } from '@ember/helper';
Ember._helperManagerCapabilities
Previously, _helperManagerCapabilities
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._helperManagerCapabilities
If needed, _helperManagerCapabilities
can be imported:
import { capabilities } from '@ember/helper';
Ember._Input
Previously, _Input
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._Input
If needed, _Input
can be imported:
import { Input } from '@ember/component';
Ember._invokeHelper
Previously, _invokeHelper
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._invokeHelper
If needed, _invokeHelper
can be imported:
import { invokeHelper } from '@ember/helper';
Ember._isDestroyed
Previously, _isDestroyed
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._isDestroyed
If needed, _isDestroyed
can be imported:
import { isDestroyed } from '@ember/destroyable';
Ember._isDestroying
Previously, _isDestroying
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._isDestroying
If needed, _isDestroying
can be imported:
import { isDestroying } from '@ember/destroyable';
Ember._modifierManagerCapabilities
Previously, _modifierManagerCapabilities
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._modifierManagerCapabilities
If needed, _modifierManagerCapabilities
can be imported:
import { capabilities } from '@ember/modifier';
Ember._on
Previously, _on
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._on
If needed, _on
can be imported:
import { on } from '@ember/modifier';
Ember._ProxyMixin
Previously, _ProxyMixin
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._ProxyMixin
_ProxyMixin
is also private.
There is no replacement for this API.
Ember._registerDestructor
Previously, _registerDestructor
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._registerDestructor
If needed, _registerDestructor
can be imported:
import { registerDestructor } from '@ember/destroyable';
Ember._RegistryProxyMixin
Previously, _RegistryProxyMixin
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._RegistryProxyMixin
_RegistryProxyMixin
is also private.
There is no replacement for this API.
Ember._setClassicDecorator
Previously, _setClassicDecorator
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._setClassicDecorator
_setClassicDecorator
is also private.
There is no replacement for this API.
Ember._setComponentManager
Previously, _setComponentManager
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._setComponentManager
If needed, _setComponentManager
can be imported:
import { setComponentManager } from '@ember/component';
Ember._setComponentTemplate
Previously, _setComponentTemplate
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._setComponentTemplate
If needed, _setComponentTemplate
can be imported:
import { setComponentTemplate } from '@ember/component';
Ember._setHelperManager
Previously, _setHelperManager
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._setHelperManager
If needed, _setHelperManager
can be imported:
import { setHelperManager } from '@ember/helper';
Ember._setModifierManager
Previously, _setModifierManager
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._setModifierManager
If needed, _setModifierManager
can be imported:
import { setModifierManager } from '@ember/modifier';
Ember._templateOnlyComponent
Previously, _templateOnlyComponent
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._templateOnlyComponent
If needed, _templateOnlyComponent
can be imported:
import templateOnly from '@ember/component/template-only';
Ember._tracked
Previously, _tracked
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._tracked
If needed, _tracked
can be imported:
import { tracked } from '@glimmer/tracking';
Ember._unregisterDestructor
Previously, _unregisterDestructor
could be accessed via the Ember
import:
import Ember from 'ember';
Ember._unregisterDestructor
If needed, _unregisterDestructor
can be imported:
import { unregisterDestructor } from '@ember/destroyable';
Ember.A
Previously, A
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.A
If needed, A
can be imported:
import { A } from '@ember/array';
Ember.ActionHandler
Previously, ActionHandler
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ActionHandler
ActionHandler
is also private.
There is no replacement for this API.
Ember.addListener
Previously, addListener
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.addListener
If needed, addListener
can be imported:
import { addListener } from '@ember/object/events';
Ember.addObserver
Previously, addObserver
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.addObserver
If needed, addObserver
can be imported:
import { addObserver } from '@ember/object/observers';
Ember.Application
Previously, Application
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Application
If needed, Application
can be imported:
import Application from '@ember/application';
Ember.ApplicationInstance
Previously, ApplicationInstance
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ApplicationInstance
If needed, ApplicationInstance
can be imported:
import ApplicationInstance from '@ember/application/instance';
Ember.Array
Previously, Array
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Array
If needed, Array
can be imported:
import Array from '@ember/array';
Ember.ArrayProxy
Previously, ArrayProxy
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ArrayProxy
If needed, ArrayProxy
can be imported:
import ArrayProxy from '@ember/array/proxy';
Ember.assert
Previously, assert
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.assert
If needed, assert
can be imported:
import { assert } from '@ember/debug';
Ember.beginPropertyChanges
Previously, beginPropertyChanges
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.beginPropertyChanges
beginPropertyChanges
is also private.
There is no replacement for this API.
Ember.BOOTED
Previously, BOOTED
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.BOOTED
BOOTED
is also private.
There is no replacement for this API.
Ember.cacheFor
Previously, cacheFor
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.cacheFor
There is no replacement for this API.
Ember.canInvoke
Previously, canInvoke
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.canInvoke
canInvoke
is also private.
There is no replacement for this API.
Ember.changeProperties
Previously, changeProperties
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.changeProperties
changeProperties
is also private.
There is no replacement for this API.
Ember.Comparable
Previously, Comparable
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Comparable
Comparable
is also private.
There is no replacement for this API.
Ember.compare
Previously, compare
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.compare
If needed, compare
can be imported:
import { compare } from '@ember/utils';
Ember.Component
Previously, Component
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Component
If needed, Component
can be imported:
import Component from '@ember/component';
Ember.computed
Previously, computed
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.computed
If needed, computed
can be imported:
import { computed } from '@ember/object';
Ember.ComputedProperty
Previously, ComputedProperty
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ComputedProperty
There is no replacement for this API.
Ember.ContainerDebugAdapter
Previously, ContainerDebugAdapter
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ContainerDebugAdapter
If needed, ContainerDebugAdapter
can be imported:
import ContainerDebugAdapter from '@ember/debug/container-debug-adapter';
Ember.Container
Previously, Container
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Container
Container
is also private.
There is no replacement for this API.
Ember.controllerFor
Previously, controllerFor
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.controllerFor
controllerFor
is also private.
There is no replacement for this API.
Ember.Controller
Previously, Controller
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Controller
If needed, Controller
can be imported:
import Controller from '@ember/controller';
Ember.ControllerMixin
Previously, ControllerMixin
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ControllerMixin
ControllerMixin
is also private.
If needed, ControllerMixin
can be imported:
import { ControllerMixin } from '@ember/controller';
However, due to ControllerMixin
being private, it is not recommended, nor supported.
Ember.CoreObject
Previously, CoreObject
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.CoreObject
If needed, CoreObject
can be imported:
import EmberObject from '@ember/object';
Ember.DataAdapter
Previously, DataAdapter
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.DataAdapter
If needed, DataAdapter
can be imported:
import DataAdapter from '@ember/debug/data-adapter';
Ember.debug
Previously, debug
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.debug
If needed, debug
can be imported:
import { debug } from '@ember/debug';
Ember.defineProperty
Previously, defineProperty
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.defineProperty
If needed, defineProperty
can be imported:
import { defineProperty } from '@ember/object';
Ember.deprecate
Previously, deprecate
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.deprecate
If needed, deprecate
can be imported:
import { deprecate } from '@ember/debug';
Ember.deprecateFunc
Previously, deprecateFunc
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.deprecateFunc
If needed, deprecateFunc
can be imported:
import { deprecateFunc } from '@ember/debug';
Ember.destroy
Previously, destroy
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.destroy
If needed, destroy
can be imported:
import { destroy } from '@ember/destroyable';
Ember.endPropertyChanges
Previously, endPropertyChanges
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.endPropertyChanges
endPropertyChanges
is also private.
There is no replacement for this API.
Ember.Engine
Previously, Engine
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Engine
If needed, Engine
can be imported:
import Engine from '@ember/engine';
Ember.EngineInstance
Previously, EngineInstance
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.EngineInstance
If needed, EngineInstance
can be imported:
import Engine from '@ember/engine/instance';
Ember.Enumerable
Previously, Enumerable
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Enumerable
Enumerable
is also private.
If needed, Enumerable
can be imported:
import Enumerable from '@ember/enumerable';
However, due to Enumerable
being private, it is not recommended, nor supported.
Ember.ENV
Previously, ENV
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ENV
If needed, ENV
can be imported:
import MyEnv from '<my-app>/config/environment';
For addons, getting access to the environment requires having access to the owner
:
import { getOwner } from '@ember/owner';
// ...
let env = getOwner(this).resolveRegistration('config:environment');
Ember.Evented
Previously, Evented
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Evented
If needed, Evented
can be imported:
import Evented from '@ember/object/evented';
Ember.expandProperties
Previously, expandProperties
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.expandProperties
If needed, expandProperties
can be imported:
import { expandProperties } from '@ember/object/computed';
Ember.FEATURES
Previously, FEATURES
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.FEATURES
If needed, FEATURES
can be imported:
import { isEnabled, FEATURES } from '@ember/canary-features';
Ember.generateControllerFactory
Previously, generateControllerFactory
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.generateControllerFactory
generateControllerFactory
is also private.
There is no replacement for this API.
Ember.generateController
Previously, generateController
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.generateController
generateController
is also private.
There is no replacement for this API.
Ember.generateGuid
Previously, generateGuid
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.generateGuid
generateGuid
is also private.
There is no replacement for this API.
Ember.get
Previously, get
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.get
If needed, get
can be imported:
import { get } from '@ember/object';
Ember.getOwner
Previously, getOwner
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.getOwner
If needed, getOwner
can be imported:
import { getOwner } from '@ember/owner';
If you're working in a library and need to support earlier than ember-source@4.11, you may use @embroider/macros
to selectively import from the old location
import {
macroCondition,
dependencySatisfies,
importSync,
} from '@embroider/macros';
let getOwner;
if (macroCondition(dependencySatisfies('ember-source', '>= 4.11'))) {
getOwner = importSync('@ember/owner').getOwner;
} else {
getOwner = importSync('@ember/application').getOwner;
}
Ember.getProperties
Previously, getProperties
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.getProperties
If needed, getProperties
can be imported:
import { getProperties } from '@ember/object';
Ember.guidFor
Previously, guidFor
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.guidFor
If needed, guidFor
can be imported:
import { guidFor } from '@ember/object/internals';
Ember.GUID_KEY
Previously, GUID_KEY
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.GUID_KEY
GUID_KEY
is also private.
There is no replacement for this API.
Ember.Handlebars
Previously, Handlebars
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Handlebars
There is no replacement for this API.
Ember.hasListeners
Previously, hasListeners
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.hasListeners
There is no replacement for this API.
Ember.HashLocation
Previously, HashLocation
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.HashLocation
If needed, HashLocation
can be imported:
import HashLocation from '@ember/routing/hash-location';
Ember.Helper
Previously, Helper
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Helper
If needed, Helper
can be imported:
import Helper from '@ember/component/helper';
Ember.HistoryLocation
Previously, HistoryLocation
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.HistoryLocation
If needed, HistoryLocation
can be imported:
import HistoryLocation from '@ember/routing/history-location';
Ember.HTMLBars
Previously, HTMLBars
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.HTMLBars
There is no replacement for this API.
Ember.inject
Previously, inject
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.inject
There is no replacement for this API.
Ember.inspect
Previously, inspect
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.inspect
inspect
is also private.
There is no replacement for this API.
Ember.instrument
Previously, instrument
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.instrument
instrument
is also private.
If needed, instrument
can be imported:
import { instrument } from '@ember/instrumentation';
However, due to instrument
being private, it is not recommended, nor supported.
Ember.Instrumentation
Previously, Instrumentation
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Instrumentation
Instrumentation
is also private.
If needed, Instrumentation
can be imported:
import { * } from '@ember/instrumentation';
However, due to Instrumentation
being private, it is not recommended, nor supported.
Ember.isArray
Previously, isArray
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.isArray
If needed, isArray
can be imported:
import { isArray } from '@ember/array';
Ember.isBlank
Previously, isBlank
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.isBlank
If needed, isBlank
can be imported:
import { isBlank } from '@ember/utils';
Ember.isEmpty
Previously, isEmpty
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.isEmpty
If needed, isEmpty
can be imported:
import { isEmpty } from '@ember/utils';
Ember.isEqual
Previously, isEqual
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.isEqual
If needed, isEqual
can be imported:
import { isEqual } from '@ember/utils';
Ember.isNamespace
Previously, isNamespace
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.isNamespace
There is no replacement for this API.
Ember.isPresent
Previously, isPresent
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.isPresent
If needed, isPresent
can be imported:
import { isPresent } from '@ember/utils';
Ember.libraries
Previously, libraries
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.libraries
There is no replacement for this API.
If needed, libraries
can be imported from a private module:
import { libraries } from '@ember/-internals/metal';
Instead of using this import, consider using a build plugin for your packager. Some options:
- https://github.com/ubilabs/webpack-node-modules-list
- https://github.com/yjl9903/unplugin-info
These are both more automatic than Ember's libraries
utility.
Ember.lookup
Previously, lookup
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.lookup
There is no replacement for this API.
Ember.makeArray
Previously, makeArray
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.makeArray
makeArray
is also private.
If needed, makeArray
can be imported:
import { makeArray } from '@ember/array';
However, due to makeArray
being private, it is not recommended, nor supported.
Ember.meta
Previously, meta
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.meta
meta
is also private.
If needed, meta
can be imported:
import { meta } from '@ember/-internals/meta';
However, due to meta
being private, it is not recommended, nor supported.
Ember.mixin
Previously, mixin
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.mixin
mixin
is also private.
If needed, mixin
can be imported:
import { mixin } from '@ember/object/mixin';
However, due to mixin
being private, it is not recommended, nor supported.
Ember.MutableArray
Previously, MutableArray
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.MutableArray
If needed, MutableArray
can be imported:
import MutableArray from '@ember/array/mutable';
Ember.MutableEnumerable
Previously, MutableEnumerable
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.MutableEnumerable
MutableEnumerable
is also private.
If needed, MutableEnumerable
can be imported:
import MutableEnumerable from '@ember/enumerable/mutable';
However, due to MutableEnumerable
being private, it is not recommended, nor supported.
Ember.Namespace
Previously, Namespace
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Namespace
If needed, Namespace
can be imported:
import Namespace from '@ember/application/namespace';
Ember.NativeArray
Previously, NativeArray
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.NativeArray
If needed, NativeArray
can be imported:
import { NativeArray } from '@ember/array';
Ember.NoneLocation
Previously, NoneLocation
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.NoneLocation
If needed, NoneLocation
can be imported:
import NoneLocation from '@ember/routing/none-location';
Ember.notifyPropertyChange
Previously, notifyPropertyChange
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.notifyPropertyChange
If needed, notifyPropertyChange
can be imported:
import { notifyPropertyChange } from '@ember/object';
Ember.Object
Previously, Object
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Object
If needed, Object
can be imported:
import Object from '@ember/object';
Ember.ObjectProxy
Previously, ObjectProxy
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ObjectProxy
If needed, ObjectProxy
can be imported:
import ObjectProxy from '@ember/object/proxy';
Ember.Observable
Previously, Observable
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Observable
If needed, Observable
can be imported:
import Observable from '@ember/object/observable';
Ember.observer
Previously, observer
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.observer
If needed, observer
can be imported:
import { observer } from '@ember/object';
Ember.on
Previously, on
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.on
If needed, on
can be imported:
import { on } from '@ember/object/evented';
Ember.onLoad
Previously, onLoad
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.onLoad
If needed, onLoad
can be imported:
import { onLoad } from '@ember/application';
Ember.onerror
Previously, onerror
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.onerror
There is no replacement for this API.
Ember.PromiseProxyMixin
Previously, PromiseProxyMixin
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.PromiseProxyMixin
If needed, PromiseProxyMixin
can be imported:
import EmberPromiseProxyMixin from '@ember/object/promise-proxy-mixin';
Ember.Registry
Previously, Registry
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Registry
Registry
is also private.
There is no replacement for this API.
Ember.removeListener
Previously, removeListener
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.removeListener
If needed, removeListener
can be imported:
import { removeListener } from '@ember/object/events';
Ember.removeObserver
Previously, removeObserver
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.removeObserver
If needed, removeObserver
can be imported:
import { removeObserver } from '@ember/object/observers';
Ember.Route
Previously, Route
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Route
If needed, Route
can be imported:
import Route from '@ember/routing/route';
Ember.RouterDSL
Previously, RouterDSL
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.RouterDSL
There is no replacement for this API.
Ember.RSVP
Previously, RSVP
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.RSVP
If needed, RSVP
can be imported:
import RSVP from 'rsvp';
Ember.run
Previously, run
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.run
If needed, run
can be imported:
import { run } from '@ember/runloop';
Ember.runInDebug
Previously, runInDebug
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.runInDebug
If needed, runInDebug
can be imported:
import { runInDebug } from '@ember/debug';
Ember.runLoadHooks
Previously, runLoadHooks
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.runLoadHooks
If needed, runLoadHooks
can be imported:
import { runLoadHooks } from '@ember/application';
Ember.sendEvent
Previously, sendEvent
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.sendEvent
If needed, sendEvent
can be imported:
import { sendEvent } from '@ember/object/events';
Ember.Service
Previously, Service
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Service
If needed, Service
can be imported:
import Service from '@ember/service';
Ember.set
Previously, set
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.set
If needed, set
can be imported:
import { set } from '@ember/object';
Ember.setOwner
Previously, setOwner
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.setOwner
If needed, setOwner
can be imported:
import { setOwner } from '@ember/owner';
If you're working in a library and need to support earlier than ember-source@4.11, you may use @embroider/macros
to selectively import from the old location
import {
macroCondition,
dependencySatisfies,
importSync,
} from '@embroider/macros';
let setOwner;
if (macroCondition(dependencySatisfies('ember-source', '>= 4.11'))) {
setOwner = importSync('@ember/owner').setOwner;
} else {
setOwner = importSync('@ember/application').setOwner;
}
Ember.setProperties
Previously, setProperties
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.setProperties
If needed, setProperties
can be imported:
import { setProperties } from '@ember/object';
Ember.setupForTesting
Previously, setupForTesting
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.setupForTesting
There is no replacement for this API.
Ember.subscribe
Previously, subscribe
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.subscribe
subscribe
is also private.
If needed, subscribe
can be imported:
import { subscribe } from '@ember/instrumentation';
However, due to subscribe
being private, it is not recommended, nor supported.
Ember.TEMPLATES
Previously, TEMPLATES
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.TEMPLATES
TEMPLATES
is also private.
There is no replacement for this API.
Ember.Test
Previously, Test
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.Test
There is no replacement for this API.
Ember.testing
Previously, testing
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.testing
There is no replacement for this API.
Ember.toString
Previously, toString
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.toString
There is no replacement for this API.
Ember.trySet
Previously, trySet
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.trySet
If needed, trySet
can be imported:
import { trySet } from '@ember/object';
Ember.typeOf
Previously, typeOf
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.typeOf
If needed, typeOf
can be imported:
import { typeOf } from '@ember/utils';
Ember.uuid
Previously, uuid
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.uuid
There is no replacement for this API.
Ember.VERSION
Previously, VERSION
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.VERSION
If needed, VERSION
can be imported:
import { VERSION } from '@ember/version';
Ember.ViewUtils
Previously, ViewUtils
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.ViewUtils
ViewUtils
is also private.
If needed, ViewUtils
can be imported:
import { isSerializationFirstNode } from '@ember/-internals/glimmer';
However, due to ViewUtils
being private, it is not recommended, nor supported.
Ember.warn
Previously, warn
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.warn
If needed, warn
can be imported:
import { warn } from '@ember/debug';
Ember.wrap
Previously, wrap
could be accessed via the Ember
import:
import Ember from 'ember';
Ember.wrap
wrap
is also private.
There is no replacement for this API.
Deprecation of ObjectProxy
ObjectProxy
is deprecated. In modern Ember, you should use tracked properties and native JavaScript patterns instead. The best replacement depends on how you were using ObjectProxy
. Some example use cases are shown below.
Recommended: Use Tracked Properties
For most use cases, the modern Ember approach is to use tracked properties directly:
import { tracked } from '@glimmer/tracking';
class PersonManager {
@tracked person = { name: 'Tom' };
// Easy to swap content and templates will update automatically
updatePerson(newPerson) {
this.person = newPerson;
}
}
This provides automatic tracking without the complexity of proxies and follows modern Ember patterns.
Advanced Use Cases
If you need more advanced behavior like content swapping with a stable reference or property interception, you can use the approaches below.
Swapping Content
If you were using ObjectProxy
to easily swap out the underlying object while keeping a stable reference, you can achieve a similar, transparent effect using a native Proxy
backed by a class with a @tracked
property.
Before:
import ObjectProxy from '@ember/object/proxy';
const person = { name: 'Tom' };
const proxy = ObjectProxy.create({ content: person });
proxy.get('name'); // 'Tom'
// Later, you can easily swap the content
proxy.set('content', { name: 'Thomas' });
proxy.get('name'); // 'Thomas'
After:
import { tracked } from '@glimmer/tracking';
// A helper class to hold the tracked state.
class SwappableState {
@tracked content;
constructor(initialContent) {
this.content = initialContent;
}
}
// A factory function to create a proxy that is transparent
// and allows swapping the underlying content.
function createSwappableObject(initialContent) {
const state = new SwappableState(initialContent);
return new Proxy(state, {
get(target, property, receiver) {
// Allow getting/setting the content directly for swapping.
if (property === 'content') {
return target.content;
}
// Delegate all other property access to the content object.
return Reflect.get(target.content, property, receiver);
},
set(target, property, value, receiver) {
// Allow setting the content directly for swapping.
if (property === 'content') {
target.content = value;
return true;
}
// Delegate all other property sets to the content object.
return Reflect.set(target.content, property, value, receiver);
},
// Add other traps to make the proxy behave like a full object.
has: (target, key) => key in target.content,
ownKeys: (target) => Reflect.ownKeys(target.content),
getOwnPropertyDescriptor: (target, key) => Reflect.getOwnPropertyDescriptor(target.content, key),
defineProperty: (target, key, desc) => Reflect.defineProperty(target.content, key, desc),
deleteProperty: (target, key) => Reflect.deleteProperty(target.content, key),
});
}
const person = createSwappableObject({ name: 'Tom' });
// Access properties transparently.
person.name; // 'Tom'
// Later, you can easily swap the content.
// Any part of your app observing this will update because
// the underlying state is tracked.
person.content = { name: 'Thomas' };
person.name; // 'Thomas'
Adding/Overriding Properties
If you had computed properties on your proxy or were using it to add or override behavior, you often don't need a proxy at all. You can simply add a getter to the object:
Before:
import ObjectProxy from '@ember/object/proxy';
import { computed } from '@ember/object';
const ProxyWithComputedProperty = ObjectProxy.extend({
fullName: computed('firstName', 'lastName', function() {
return `${this.get('firstName')} ${this.get('lastName')}`;
})
});
let proxy = ProxyWithComputedProperty.create({
content: { firstName: 'Tom', lastName: 'Dale' }
});
proxy.get('fullName'); // 'Tom Dale'
After:
const person = {
firstName: 'Tom',
lastName: 'Dale',
get fullName() {
// or person.firstName person.lastName
return `${this.firstName} ${this.lastName}`;
}
};
person.fullName; // 'Tom Dale'
person.firstName; // 'Tom'
If you need to add properties to an object you can't modify, you can use a native Proxy
:
const person = { firstName: 'Tom', lastName: 'Dale' };
const personProxy = new Proxy(person, {
get(target, property, receiver) {
if (property === 'fullName') {
return `${target.firstName} ${target.lastName}`;
}
return Reflect.get(target, property, receiver);
}
});
personProxy.fullName; // 'Tom Dale'
personProxy.firstName; // 'Tom'
Handling Unknown Properties (unknownProperty
)
For more advanced use cases, ObjectProxy
provided an unknownProperty
hook to handle access to properties that don't exist. The get
trap in a native Proxy
provides the same capability.
Before:
import ObjectProxy from '@ember/object/proxy';
const proxy = ObjectProxy.extend({
unknownProperty(key) {
return `Property '${key}' does not exist.`;
}
}).create({
content: { a: 1 }
});
proxy.get('a'); // 1
proxy.get('b'); // "Property 'b' does not exist."
After:
const data = { a: 1 };
const handler = {
get(target, key, receiver) {
if (key in target) {
return Reflect.get(target, key, receiver);
}
return `Property '${key}' does not exist.`;
}
};
const proxy = new Proxy(data, handler);
proxy.a; // 1
proxy.b; // "Property 'b' does not exist."
Migration Strategy
When migrating from ObjectProxy
, consider:
- First choice: Use
@tracked
properties and direct object access - For computed properties: Add getters directly to objects when possible
- Only if needed: Use native
Proxy
for dynamic property access or when you can't modify the original object
The modern Ember approach favors explicit tracked properties and direct object access over proxy-based solutions, which are easier to understand, debug, and have better performance characteristics.
Deprecation of PromiseProxyMixin
PromiseProxyMixin
is deprecated. You should use native async/await
and Promises directly to manage asynchronous operations and their state.
PromiseProxyMixin
was used to create proxy objects that represented the eventual result of a promise, with properties to track the promise's lifecycle (isPending
, isFulfilled
, etc.).
Replacing PromiseProxyMixin
The modern approach is to use a class (like a component or service) to manage the state of the asynchronous operation.
Before:
import ObjectProxy from '@ember/object/proxy';
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
const PromiseObject = ObjectProxy.extend(PromiseProxyMixin);
const promise = new Promise(resolve => resolve({ value: 42 }));
const proxy = PromiseObject.create({ promise });
// In a template, you might have:
// {{#if proxy.isPending}}
// Loading...
// {{else if proxy.isFulfilled}}
// Value: {{proxy.content.value}}
// {{else if proxy.isRejected}}
// Error: {{proxy.reason}}
// {{/if}}
After (using async/await
and tracked properties in a component):
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { task } from 'ember-concurrency';
export default class MyComponent extends Component {
@task
*loadData() {
try {
const promise = new Promise(resolve => resolve({ value: 42 }));
const content = yield promise;
return content;
} catch (e) {
// ember-concurrency provides its own error state
throw e;
}
}
get lastTask() {
return this.loadData.last;
}
<template>
{{#if this.lastTask.isRunning}}
Loading...
{{else if this.lastTask.isSuccessful}}
Value: {{this.lastTask.value}}
{{else if this.lastTask.error}}
Error: {{this.lastTask.error}}
{{/if}}
</template>
}
For simpler cases where you don't need the full power of a library like ember-concurrency
, you can manage the state manually with @tracked
properties:
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class MyComponent extends Component {
@tracked isLoading = true;
@tracked error = null;
@tracked content = null;
constructor() {
super(...arguments);
this.loadData();
}
async loadData() {
try {
this.isLoading = true;
const promise = new Promise(resolve => resolve({ value: 42 }));
this.content = await promise;
} catch (e) {
this.error = e;
} finally {
this.isLoading = false;
}
}
<template>
{{#if this.isLoading}}
Loading...
{{else if this.content}}
Value: {{this.content.value}}
{{else if this.error}}
Error: {{this.error}}
{{/if}}
</template>
}
Using a library like ember-concurrency is highly recommended for managing concurrent asynchronous user-initiated tasks in Ember applications, as it provides robust solutions for handling loading/error states, cancellation, and more.
For data loading specifically, you may also want to consider using WarpDrive (formerly Ember Data) which provides a number of utilities around tracking for data.
Migration Strategy
When migrating from PromiseProxyMixin
, consider:
- First choice: Use
ember-concurrency
for user-initiated async tasks (button clicks, form submissions) - For data loading: Consider
getRequestState
from warp-drive for request state management - For simple cases: Use
@tracked
properties withasync/await
and manual state management
The modern Ember approach uses explicit async/await patterns and proper state management libraries rather than proxy-based promise wrappers.