Decorators
Summary
Native classes are now officially supported in Ember, but currently their usage is very limited. Core Ember features such as computed properties, actions, and service injections have no publicly supported APIs to be used with native class syntax.
Decorators provide a way to abstract functionality and improve the developer experience of working with native classes. This RFC outlines the implementation and rollout plan for bringing decorators to Ember's computed properties (and other behavior) for use in native classes.
A Note on Decorator Stability
Decorators are important to adopting native class syntax. They are a formalization of the patterns we have been using as a community for years, and it will not be possible to use native classes ergonomically without them unless a number of major concepts (computed properties, injections, actions) are rethought. That said, as of today (01/03/19) decorators are still a stage 2 proposal in TC39, which means that while they are fully defined as a spec, they are not yet considered a candidate for inclusion in the language, and may have incremental changes that could be breaking if/when moved to stage 3. As such, merging support for them now would pose some risk. Additionally, class fields are also required for effective use of decorators, and while they are stage 3 in the process, they have not yet been fully accepted either.
Ember cannot guarantee that the spec won't change, and such changes cannot apply to Ember's normal semver guarantees. But it can make the following guarantees:
If there are changes to the spec, and it is possible to avoid changing the public APIs of decorators, then Ember will make the changes necessary to avoid public API changes.
If there are changes to the spec, and it is not possible to avoid breaking changes, Ember will minimize the changes as much as possible, and will provide a codemod to convert from the previous version of the spec to the next.
If the spec is dropped from TC39 altogether, Ember would have to continue to provide support for decorators via babel transforms until they are deprecated following the standard RFC process, and removed according to SemVer. Reverse codemods which translate decorators and native class syntax back to classic class syntax will be made, and alternatives for native class syntax will be explored.
Classic class syntax will continue to be supported at least until these features have been stabilized in the JavaScript language, to allow us to revert these changes if necessary. They will most likely be supported for longer to allow a smooth transition for users who do not want to adopt native classes until they are completely stable.
This RFC is being made with the assumption that decorators will be moved to stage 3 in the near future, before this RFC is implemented in Ember, dramatically reducing the risk of adopting decorators. If this RFC is accepted and decorators are not advanced in a timely manner, a followup RFC should be made to determine whether or not decorators should be adopted in stage 2, and what the support for them would look like.
Terminology
For the purposes of this RFC, we'll use the following terminology:
- The Octane programming model refers to the new programming model established by the Ember Octane edition. It includes native classes, tracked properties and Glimmer components, and more generally refers to features that will be considered core to Ember in the future.
- The classic programming model refers to the traditional programming model. It includes classic classes, computed properties, event listeners, observers, property notifications, and classic components, and more generally refers to features that will not be central to Ember Octane.
- Native classes are classes defined using the JavaScript
class
keyword - Classic classes are classes defined by subclassing from
EmberObject
using the staticextend
method.
Motivation
Native JavaScript class syntax has been evolving for the past three years, filling in
the cracks and providing better, more standardized ways to write classes for the
web. They will be a key part of the Octane programming model, and the ES Classes
RFC was the first step toward enabling Ember users to use native class syntax,
but there are still key features of Ember that are not usable with class
syntax today, including computed properties, actions, and injections.
These features cannot be used ergonomically with native classes as it stands.
The only options are to either use Ember's defineProperty
function directly,
or to define these values in an anonymous class. Both of these options are hard
to read, and will be difficult to codemod in the future:
// Using define property
import { computed, defineProperty } from '@ember/object';
class Person {
constructor() {
this.firstName = 'Melanie';
this.lastName = 'Sumner';
}
}
// define a computed property
defineProperty(
Person.prototype,
'fullName',
computed('firstName', 'lastName', {
get() {
return `${this.firstName} ${this.lastName}`;
}
})
);
// Using intermediate extends
import EmberObject, { computed } from '@ember/object';
class Person extends EmberObject.extend({
fullName: computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
})
}) {
constructor() {
super(...arguments);
this.firstName = 'Melanie';
this.lastName = 'Sumner';
}
}
Another method which was used for some time was to assign these values using class field initializers. This practice is problematic however as it creates a new instance of the computed property per instance of the class, and does not work with native getters either:
import EmberObject, { computed } from '@ember/object';
export default class Person extends EmberObject {
firstName = 'Melanie';
lastName = 'Sumner';
fullName = computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
});
}
new Profile().fullName; // returns the CP instance, not 'Melanie Sumner'
The missing piece of functionality that we need here are decorators, and in fact that is not a coincidence. The roots of the current TC39 decorator proposal can be traced back to Ember (Yehuda having worked on the first few drafts of it) specifically because computed properties, observers, and so on are decorators. We've been using them for years in Ember, just with a non-standard, slightly-less-clean syntax.
import Component from '@ember/component';
import { computed } from '@ember/object';
export default class Profile extends Component {
firstName = 'Melanie';
lastName = 'Sumner';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
Native decorators bring a more natural way of declaring computed properties to native classes than the current computed macros.
Prior Art
The Ember Decorators project has been experimenting with using decorators within Ember for some time now, with the goal of reaching feature parity with the classic object model, and the learnings from that project will be used to inform the API design in this RFC.
Detailed design
This RFC proposes that:
Ember.computed
, theinject
macros, and the computed property macros be updated to return a standard JavaScript decorator. Internally, the classic object model will be made aware of these decorators, and know how to apply them. This will allow the same exact imports to continue working as both native decorators and classic decorators. This decorator will be compatible with classes that extend fromEmberObject
and classes which do not.- A new
@action
decorator be added to allow the definition of actions on classes. This decorator will only be compatible with classes that are action handlers.
This does leave out some features from the classic programming model which are currently provided by Ember Decorators. This is both to minimize decorators' API surface area, and because they will not be a major part of Ember Octane's programming model. Addressing them individually:
- Observers and event listeners, which have long been considered an antipattern.
- Classic component functionality such as
classNames
,classNameBindings
,attributeBindings
, etc. will be unnecessary with Glimmer components. - Ember Data provides computed properties which had to be manually wrapped in decorators. With the changes proposed in this RFC, however, they should continue to work without any additional changes. In fact, all computed property macros will.
Users who want these features will still be able to rely on addons such as Ember Decorators, which will provide decorator support for them for the forseeable future. Moving forward, this RFC breaks down into computed properties and actions.
Computed Properties
As mentioned before, computed properties essentially are decorators. However,
they are not spec compliant. Currently, computed()
returns an instance of the
ComputedProperty
class, which contains all of the meta information about the
decorated property. Native decorators, by contrast, are functions which receive
a descriptor and modify it as necessary.
Unfortunately, there's no way for us to know ahead of time when a computed property is going to be used as a native decorator in a native class, and when it is going to be used in a classic class. Consider the following:
class Person {
@alias('prefix') title;
}
Really, what's going on there is not that we are invoking the @alias
decorator with parameters. We are invoking a function which returns a
decorator, so it desugars to:
const aliasForPrefix = alias('prefix');
class Person {
@aliasForPrefix title;
}
Therefore, the alias
function must itself return a decorator function.
However, this conflicts with usage in the classic programming model:
const Person = EmberObject.extend({
title: alias('prefix')
})
We just established that alias
must return a decorator function, but here it
is with the exact same arguments, and it needs to return a ComputedProperty
instance. There is nothing we can branch on here - in both cases, alias
only
receives the string 'prefix'
, so it has no context for how it will be used.
The native class piece of this puzzle is completely inflexible. A decorator must
be a function, there is no choice about it. However, the Ember piece is very
flexible. The classic object model just needs a way to get the meta information
for the property when the class is being finalized. We can either assign the
meta information to the decorator function directly, or we can associate it via
a WeakMap
.
The benefit of doing this is that the entire Ember ecosystem will get decorator
support with no extra work required. Because standard computed
definitions
will work as decorators, existing macros will also work as decorators with no
changes to existing code.
All of Ember's built in computed macros in the @ember/object/computed
module
will also become decorators with no extra work. However, the injection macros
will require slight updates as they use a subclass of the ComputedProperty
class. These updates should be relatively minor, and will follow the same
strategy as computed()
.
Usage and API
The API for computed
will remain mostly the same. The key differences will be:
- The result of
computed
will be a decorator which can be applied directly to native getters, setters, and class fields. - The
ComputedPropertyConfig
(the getter/setter functions) argument provided tocomputed
will now be optional when used as a decorator on a native getter or setter, and the native getter/setter will be used instead.
function computed(...args: (string | ComputedPropertyConfig)[]): PropertyDecorator;
The function signatures of all existing macros, including inject
macros, would
change in the same way.
In general usage, these three definitions are equivalent:
import EmberObject, { computed } from '@ember/object';
const Person = EmberObject.extend({
fullName: computed('firstName', 'lastName', {
get() {
return `${this.firstName} ${this.lastName}`;
}
})
});
class Person {
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
class Person {
@computed('firstName', 'lastName', {
get() {
return `${this.firstName} ${this.lastName}`;
}
})
fullName;
}
That last example may seem confusing at first, but this is actually the same as defining a computed property macro:
import EmberObject, { computed } from '@ember/object';
function join(...dependentKeys) {
return computed(...dependentKeys, {
get() {
return dependentKeys.map(key => this[key]).join(' ');
}
});
}
class Person {
@join('firstName', 'lastName')
fullName;
}
Notably, using @computed
as a decorator directly, without parenthesis, will
not be supported. This is to prevent a parameter check on a critical path:
class Person {
@computed
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
The most common use case for this form in classic classes was to provide a new instance of an object or array per instance of the class. This use case is solved in native classes by class fields:
const Person = EmberObject.extend({
cache: computed(function() {
return {};
})
})
class Person {
cache = {};
}
For other use cases, such as lazy evaluation and caching, it will still be
possible to call @computed
with no arguments:
class Person {
@computed()
get cache() {
return {};
};
}
Preventing Incoherent Usage
Making the ComputedPropertyConfig
optional opens up lots of room for
accidents. A computed property without a getter or setter does not make sense,
nor does a computed propery with two getters or setters. The new decorator
will assert at decorator application time to ensure it is being used
correctly:
import EmberObject, { computed } from '@ember/object';
// This will throw because the user attempted to define a CP without a getter
const Person = EmberObject.extend({
fullName: computed()
});
// This will also throw because it is missing a getter
class Person {
@computed('firstName', 'lastName')
fullName;
}
// This will throw because a getter was already defined
class Person {
@computed('firstName', 'lastName', {
get() {
return `${this.firstName} ${this.lastName}`;
}
})
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
Effectively, if computed
is passed a ComputedPropertyConfig
, it returns a
class field decorator. Otherwise, it returns an accessor (getter/setter)
decorator.
Property Modifiers
Almost all computed property modifiers have been deprecated at this point, but they are still in use today and will still be available until Ember v4. As such, their syntax needs to remain available and unchanged:
import EmberObject, { computed } from '@ember/object';
const Person = EmberObject.extend({
fullName: computed('firstName', {
get() {
return `${this.firstName} ${this.lastName}`;
}
}).readOnly().volatile().property('lastName') // a very strange combination
});
The decorator returned from computed()
will need to have these chainable
methods available, and they will need to set the state of the decorator. This
should not be too difficult to accomplish.
Usage in native decorator syntax is a little bit trickier. In the current proposal, only simple chaining is allowed in a decorator invocation. You may not chain on the result of a function:
import { computed } from '@ember/object';
const fullName = computed('firstName', 'lastName', function() {
return `${this.firstName} ${this.lastName}`;
});
class Person {
@computed('firstName', 'lastName').readOnly() // this is invalid JS decorator syntax
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@fullName.readOnly() // this is valid because it's a simple chain
otherFullName;
}
Luckily, there is one other form of invocation which is available - wrapping the entire decorator expression in parenthesis:
import { computed } from '@ember/object';
class Person {
@(computed('firstName', 'lastName').readOnly()) // this is valid
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
This is clearly not ideal for commonly used features, and while volatile()
and property()
are not very well known, readOnly()
is generally considered
best practice, and is used all over the place. However, it is deprecated, and
in the future computed properties will be read only by default. Rather than
attempt to write a different API for decorators, this RFC proposes that we
accept the current syntax, and focus instead on the implementation of Svelte.
This will allow users to enable default read only CPs much sooner, and prevent
the need to use readOnly()
at all.
A Tale of Two readOnly
s
You may be wondering why we can't add more decorators to Ember to take the place
of these modifiers. The crux of the issue is the readOnly()
modifier, and the
readOnly()
macro. These share a name, and when macros become decorators as
well they will collide. The only difference would be the import paths, and this
would result in awkward renaming which would likely not be conventional:
import { readOnly, computed } from '@ember/object';
import { readOnly as readOnlyAlias } from '@ember/object/computed';
class Person {
@readOnly
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@readOnlyAlias('fullName')
otherFullName;
}
Ember Decorators made the decision to attempt renaming the macros themselves,
since reads
, readOnly
, and oneWay
were all poorly named (arguably, reads
should have been readOnly
since it is the more common use case) but this is
not a possibility in Ember, since this would cause collisions and confusion
within the macros namespace.
All of this would be to support a deprecated feature, which can still be used
(albeit, with a less-than-ideal syntax). Accepting the current syntax would also
prevent us from polluting the decorator namespace - we may want to use
@readOnly
or @volatile
in the future with tracked properties instead.
Actions
Actions are special in Ember because they are namespaced within a class on the
actions
object, despite being able to reference the class directly using
this
when called, and otherwise behaving like standard methods. The reason for
this stems from the early days of Ember, when users would accidentally name an
action something that conflicted with existing lifecycle and event hooks (e.g.
destroy
, or click
). The actions
namespace was added as a convenience to
prevent these collisions, and to separate them from the rest of the class body
organizationally.
This namespace is problematic for native classes for a number of reasons:
- The namespace cannot be defined using class field syntax, since that would assign a copy of the object to every instance of the class, and there is no other native way to easily assign it.
- Actions must inherit from the parent, which means that the
actions
object must have its prototype set to the parent class'sactions
object. - It is not possible to use
super
within non-class methods, meaning an alternative would have to be developed specifically for the namespace.
With these constraints, a decorator would be necessary to maintain the current namespacing functionality. The options for such a decorator are limited. It could:
Decorate a class field, placing the value on the class prototype and setting up inheritance/super functionality. This would necessarily result in either some amount of repetition in the decorator name and field name, or a redundant field name:
class Foo { @actions actions = { onClick() { /* ... */ } } }
Decorate the class itself with the actions object as a parameter. This would be awkward, since actions would be removed from the class definition:
@actions({ onClick() { /* ... */ } }) class Foo {}
Decorate the class itself, with the expectation that the
actions
class field exists and is an object. This leads to a disconnect between the decoration and the definition that is easy to miss, and could be counterintuitive to newcomers:@actionHandler class Foo { actions = { onClick() { /* ... */ } } }
None of these options is ergonomic in the least. Instead, it is much cleaner and easier to decorate method definitions that are directly on the class body:
class Foo {
@action
onClick() { /* ... */ }
}
This would be implemented by creating the class's actions
object and assigning
the method to it, setting up inheritance and such in the process. The decorator
leaves the method definition on the class, where it can be called like a normal
method would, including super
functionality. This maintains compatibility with
the classic object model, while making the actions
namespace an
implementation detail rather than something users need to know about.
This does mean that actions can once again collide with actual lifecycle and
event hooks on the class, since they are no longer namespaced. The decorator
could remove the method from the class definition entirely, but this would be
confusing for users not familiar with the old actions
namespace - why does
this method disappear from the class? It would also break super
functionality,
so it would not be ideal to do this.
The @action
decorator could warn users when it collides with a lifecycle
hook. However, hooks may vary from class type to class type, which presents a
design challenge. We could either:
- Allow classes to specify lifecycle hooks, and throw whenever
@action
collides with a specified hook. - Only throw on hooks that are shared across all classes, such as
init
anddestroy
. - Do nothing, and leave it to user's to know which lifecycle hooks exist.
Specifying hooks for each class would be time consuming and could fall out of
sync with the implementations. Throwing on "universal" hooks only is
inconsistent, and could lead users to think that they are safe when they are
not. This RFC suggests that we choose option 3 for this reason. When the actions
namespace was introduced, lifecycle hooks like destroy
and click
were less
commonly known and used (event listeners were not uncommon). Most Ember users
know they exist now, and will be aware that implementing an action with the same
name is not recommended. In addition, eslint rules can be added to hint against
these collisions.
Method Binding
@action
will also bind the function to the class instance, allowing it to be
used in templates and elsewhere without having to be bound:
export default class ButtonComponent extends Component {
@action
onClick() {
// handle click
}
}
<button onclick={{this.onClick}}>Click me!</button>
Usage and API
The API for this new decorator would be much simpler than the computed API, since it is only used as a decorator without parameters.
// Technically `action` is a function, but we can't type it transparently that way
const action: MethodDecorator;
Attempting to pass any parameters to the decorator, or to apply the decorator to anything other than a class method, will throw an error.
How we teach this
Teaching decorators is intrinsically tied to a wider shift in the Ember programming model - the Ember Octane edition. From a teaching perspective, this edition will be completely overhauling the guides and updating all of the best practices as they stand. New users should see native class syntax with decorators as the default, and should not ever have to write a classic class or see an example for one.
With this RFC, the majority of existing examples in the Ember guides will be updatable to native class syntax. The exception would be examples of classic components, which would be addressed separately by updating the guides to Glimmer Components (proposed in a separate RFC). Otherwise, all examples in the guides should be updated.
Updating and Interop
For existing users, or users who have to interact with classic code from a modern
context, it'll be important to have a reference for the classic object model.
The current section on the object model in the guides can be moved to a classic
section, and a section on updating should be added. Links to relevant codemods,
such as the
ember-es6-class-codemod,
should be included. This section should remain updated and included in the main
guides for as long as EmberObject
is a part of Ember's public API.
Acceptance Commitments
This section serves to capture the various commitments accepting this RFC would entail.
- Adds support for decorators and class fields to Ember's public API. Transforms would be included out of the box as well.
- Allows computed properties to work as a native decorators on native classes.
- Adds the
@action
decorator for defining actions on native classes.
Drawbacks
- The
ComputedProperty
class has long been considered intimate API. Even with recent changes as part of the native getter RFC to make it more private, these changes could still cause breakage. - The strategy for converting
computed
to decorators has one major drawback, which is that decorator macros cannot easily be customized and will require a bit of boilerplate in some cases. For instance, currently in Ember Decorators it is possible to apply themap
andreduce
macros directly to a method, which becomes the method to map or reduce by:
@map('array')
mappedArray() {}
@map('array', function() {}) mappedArray;
With this method, only the second form would be usable. Likewise, by default addons like Ember Data would need to write thin decorator wrappers around macros that may be called without parameters, or which require key reflection:
@attr name; // This would not work OOTB
@attr('string') name; // This would
@belongsTo user; // This would not work OOTB
@belongsTo('user') user; // This would
Alternatives
No Decorators
We could not have official Ember support for any aspects of the classic
programming model. This essentially means computed properties, since @action
and the injection helpers are still needed for the Octane model. This would
leave many users in limbo, unable to update to native class syntax fully because
it would mean rewriting large amounts of classes and components, and would make
libraries like @ember-decorators
essential.
Namespaced Decorators
We could include decorators as a separate package, such as @ember/decorators
.
This is not ideal as it would force users to remember more import paths, and it
would make eventual deprecation of the classic form much more difficult. It would
also mean that the wider ecosystem would have to do much more work to adopt
decorator syntax.
Full Compatibility Decorators
We could include decorators for the remaining classic features: Observers, event listeners, and classic components. These would add extra weight, and may encourage users to continue using these features, which would not be ideal.
Instead, we can recommend that users wanting to update to native class syntax
use external packages that implement these features, such as
@ember-decorators
. The native class codemod will detect and automatically
include these packages if they are necessary.
Default Read Only Decorator
In this proposal, computeds used as decorators match the semantics of computeds used in classic classes exactly. This is true even in the unfortunate case of computed overridability:
class Person {
firstName = 'Stefan';
lastName = 'Penner';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
}
let person = new Person;
person.fullName; // 'Tom Dale'
person.set('firstName', 'Kris');
person.set('lastName', 'Selden');
person.set('fullName', 'Melanie Sumner'); // overrides the setter
person.fullName; // 'Melanie Sumner'
We could, instead, make decorators apply computeds as readOnly by default,
since this is a new usage of them and not a breaking change. This would require
us to add a new overridable()
modifier to opt-out of readOnly behavior by
default, for backwards compatibility, and would add a fair amount of complexity
to codemods. Either way, this behavior will be the default in Ember v4, and
deprecations will begin appearing when this happens soon.
Allow @action
to Rename Actions
If the namespace collisions caused by actions becoming standard methods are
difficult to refactor around or codemod, we can consider allowing the @action
helper to receive an alternative action name:
export default class ButtonComponent extends GlimmerComponent {
@action('destroy')
destroyAction() {
// handle click
}
}
<button {{action 'destroy'}}>Click me!</button>
This could be added later by a followup RFC, so it is not part of this proposal. Ideally it won't be necessary.
Unresolved questions
As stated in the introduction, this RFC is being made with the assumption that decorators will be moved to stage 3 before this RFC is actually implemented. If they are not moved to stage 3, we will have to decide if decorators should be supported in while they are in stage 2.
If they are supported while in stage 2, there are some additional questions:
- Should stage 2 transforms continue to be supported after decorators move to stage 3? Would removing stage 2 support require a major version bump?
- Should Typescript's stage 1-like decorators be supported, since Typescript will not implement new decorator transforms until they reach stage 3?
Appendix A
This appendix contains the list of affected APIs and new APIs for quick reference.
@ember/controller |
---|
inject |
@ember/object |
---|
computed |
action |
@ember/object/computed |
---|
alias |
and |
bool |
collect |
deprecatingAlias |
empty |
equal |
filter |
filterBy |
gt |
gte |
intersect |
lt |
lte |
map |
mapBy |
match |
max |
min |
none |
not |
notEmpty |
oneWay |
or |
readOnly |
reads |
setDiff |
sort |
sum |
union |
uniq |
uniqBy |
@ember/service |
---|
inject |
Appendix B
This appendix contains a full list of changed APIs and their old and new signatures.
@ember/controller
inject
// old
function inject(): ComputedProperty<Controller>;
function inject<K extends keyof ControllerRegistry>(
name: K
): ComputedProperty<ControllerRegistry[K]>;
// new
function inject(): PropertyDecorator;
function inject<K extends keyof ControllerRegistry>(
name: K
): PropertyDecorator;
@ember/object
computed
// old
function computed(...args: (string | ComputedPropertyConfig<T>)[]): ComputedProperty<T>;
// new
function computed(...args: (string | ComputedPropertyConfig)[]): PropertyDecorator;
action
// old
// N/A
// new
const action: MethodDecorator;
**@ember/object/computed
alias
// old
function alias(dependentKey: string): ComputedProperty<any>;
// new
function alias(dependentKey: string): PropertyDecorator;
and
// old
function and(...dependentKeys: string[]): ComputedProperty<boolean>;
// new
function and(...dependentKeys: string[]): PropertyDecorator;
bool
// old
function bool(dependentKey: string): ComputedProperty<boolean>;
// new
function bool(dependentKey: string): PropertyDecorator;
collect
// old
function collect(...dependentKeys: string[]): ComputedProperty<any[]>;
// new
function collect(...dependentKeys: string[]): PropertyDecorator;
deprecatingAlias
// old
function deprecatingAlias(
dependentKey: string,
options: { id: string; until: string }
): ComputedProperty<any>;
// new
function deprecatingAlias(
dependentKey: string,
options: { id: string; until: string }
): PropertyDecorator;
empty
// old
function empty(dependentKey: string): ComputedProperty<boolean>;
// new
function empty(dependentKey: string): PropertyDecorator;
equal
// old
function equal(dependentKey: string, value: any): ComputedProperty<boolean>;
// new
function equal(dependentKey: string, value: any): PropertyDecorator;
filter
// old
function filter(
dependentKey: string,
callback: (value: any, index: number, array: any[]) => boolean
): ComputedProperty<any[]>;
// new
function filter(
dependentKey: string,
callback: (value: any, index: number, array: any[]) => boolean
): PropertyDecorator;
filterBy
// old
function filterBy(
dependentKey: string,
propertyKey: string,
value?: any
): ComputedProperty<any[]>;
// new
function filterBy(
dependentKey: string,
propertyKey: string,
value?: any
): PropertyDecorator;
gt
// old
function gt(dependentKey: string, value: number): ComputedProperty<boolean>;
// new
function gt(dependentKey: string, value: number): PropertyDecorator;
gte
// old
function gte(dependentKey: string, value: number): ComputedProperty<boolean>;
// new
function gte(dependentKey: string, value: number): PropertyDecorator;
intersect
// old
function intersect(...propertyKeys: string[]): ComputedProperty<any[]>;
// new
function intersect(...propertyKeys: string[]): PropertyDecorator;
lt
// old
function lt(dependentKey: string, value: number): ComputedProperty<boolean>;
// new
function lt(dependentKey: string, value: number): PropertyDecorator;
lte
// old
function lte(dependentKey: string, value: number): ComputedProperty<boolean>;
// new
function lte(dependentKey: string, value: number): PropertyDecorator;
map
// old
function map<U>(
dependentKey: string,
callback: (value: any, index: number, array: any[]) => U
): ComputedProperty<U[]>;
// new
function map<U>(
dependentKey: string,
callback: (value: any, index: number, array: any[]) => U
): PropertyDecorator;
mapBy
// old
function mapBy(dependentKey: string, propertyKey: string): ComputedProperty<any[]>;
// new
function mapBy(dependentKey: string, propertyKey: string): PropertyDecorator;
match
// old
function match(dependentKey: string, regexp: RegExp): ComputedProperty<boolean>;
// new
function match(dependentKey: string, regexp: RegExp): PropertyDecorator;
max
// old
function max(dependentKey: string): ComputedProperty<number>;
// new
function max(dependentKey: string): PropertyDecorator;
min
// old
function min(dependentKey: string): ComputedProperty<number>;
// new
function min(dependentKey: string): PropertyDecorator;
none
// old
function none(dependentKey: string): ComputedProperty<boolean>;
// new
function none(dependentKey: string): PropertyDecorator;
not
// old
function not(dependentKey: string): ComputedProperty<boolean>;
// new
function not(dependentKey: string): PropertyDecorator;
notEmpty
// old
function notEmpty(dependentKey: string): ComputedProperty<boolean>;
// new
function notEmpty(dependentKey: string): PropertyDecorator;
oneWay
// old
function oneWay(dependentKey: string): ComputedProperty<any>;
// new
function oneWay(dependentKey: string): PropertyDecorator;
or
// old
function or(...dependentKeys: string[]): ComputedProperty<boolean>;
// new
function or(...dependentKeys: string[]): PropertyDecorator;
readOnly
// old
function readOnly(dependentKey: string): ComputedProperty<any>;
// new
function readOnly(dependentKey: string): PropertyDecorator;
reads
// old
function reads(dependentKey: string): ComputedProperty<any>;
// new
function reads(dependentKey: string): PropertyDecorator;
setDiff
// old
function setDiff(setAProperty: string, setBProperty: string): ComputedProperty<any[]>;
// new
function setDiff(setAProperty: string, setBProperty: string): PropertyDecorator;
sort
// old
function sort(
itemsKey: string,
sortDefinition: string | ((itemA: any, itemB: any) => number)
): ComputedProperty<any[]>;
// new
function sort(
itemsKey: string,
sortDefinition: string | ((itemA: any, itemB: any) => number)
): PropertyDecorator;
sum
// old
function sum(dependentKey: string): ComputedProperty<number>;
// new
function sum(dependentKey: string): PropertyDecorator;
union
// old
function union(...propertyKeys: string[]): ComputedProperty<any[]>;
// new
function union(...propertyKeys: string[]): PropertyDecorator
uniq
// old
function uniq(propertyKey: string): ComputedProperty<any[]>;
// new
function uniq(propertyKey: string): PropertyDecorator;
uniqBy
// old
function uniqBy(dependentKey: string, propertyKey: string): ComputedProperty<any[]>;
// new
function uniqBy(dependentKey: string, propertyKey: string): PropertyDecorator;
@ember/service
inject
// old
function inject(): ComputedProperty<Service>;
function inject<K extends keyof ServiceRegistry>(
name: K
): ComputedProperty<ServiceRegistry[K]>;
// new
function inject(): PropertyDecorator;
function inject<K extends keyof ServiceRegistry>(
name: K
): PropertyDecorator;