💼 This rule is enabled in the ✅ recommended
config.
🔧 This rule is automatically fixable by the --fix
CLI option.
Ember 3.13 added an assertion that fires when using assignment this.x = 123
on an untracked property that is used in a tracking context such as a computed property.
You attempted to update "propertyX" to "valueY", but it is being tracked by a tracking context, such as a template, computed property, or observer.
In order to make sure the context updates properly, you must invalidate the property when updating it.
You can mark the property as
@tracked
, or use@ember/object#set
to do this.
This rule catches assignments of untracked properties that are used as computed property dependency keys.
Examples of incorrect code for this rule:
import { computed } from '@ember/object';
import Component from '@ember/component';
class MyComponent extends Component {
@computed('x') get myProp() {
return this.x;
}
myFunction() {
this.x = 123; // Not okay to use assignment here.
}
}
Examples of correct code for this rule:
import { computed, set } from '@ember/object';
import Component from '@ember/component';
class MyComponent extends Component {
@computed('x') get myProp() {
return this.x;
}
myFunction() {
set(this, 'x', 123); // Okay because it uses set.
}
}
import { computed, set } from '@ember/object';
import Component from '@ember/component';
import { tracked } from '@glimmer/tracking';
class MyComponent extends Component {
@tracked x;
@computed('x') get myProp() {
return this.x;
}
myFunction() {
this.x = 123; // Okay because `x` is a tracked property.
}
}
The autofixer for this rule will update assignments to use set
. Alternatively, you can begin using tracked properties.
- object -- containing the following properties:
- array --
extraMacros
-- Array of configurations for custom computed property macros which have dependent keys as arguments, each with hte following properties:- string --
name
-- The name the macro is exported with - string --
path
-- The file path used for importing the macro - string --
indexName
-- If this macro can also be imported through an index (likecomputed
forcomputed.and
), include it here - string --
indexPath
-- The path for importing the index. For example, withimport { computed } from '@ember/object'
andcomputed.and(...)
,@ember/object
is theindexPath
andcomputed
is theindexName
. - array --
argumentFormat
-- array of configurations for how to parse the arguments of the macro to extract the computed dependencies, with at least one of the following properties:- object --
strings
-- Configuration for extracting raw strings from the argument list, with the following options:- number --
count
-- How many arguments to consider as dependencies. UseNumber.MAX_VALUE
for all of them. - number --
startIndex
-- Defaults to zero. If it's something else, that many arguments will be skipped before checking forcount
dependencies.
- number --
- object --
objects
-- Configuration for extracting the values of an object as dependency keys, with the following properties:- number --
index
-- The index of the argument to be checked. - array --
keys
-- Array of strings for which keys values should be checked for. If not provided, all values will be checked.
- number --
- object --
- string --
- array --
Example configuration:
module.exports = {
rules: {
'ember/no-assignment-of-untracked-properties-used-in-tracking-contexts': {
extraMacros: [
{
name: 'rejectBy',
path: 'custom-macros/macros',
indexName: 'customComputed',
indexPath: 'custom-macros',
argumentFormat: [
{
strings: {
count: 1
}
}
]
},
{
name: 't',
path: 'ember-intl',
argumentFormat: [
{
objects: {
index: 1
}
}
]
}
]
}
}
};
This configuration works for the t macro from ember-intl, and a custom rejectBy
macro that behaves similarly to filterBy
(with the second string argument not being a dependency):
import { A, isArray } from '@ember/array';
import { get } from '@ember/object';
export default function rejectBy(dependentKey, propertyKey, value) {
return computed(`${dependentKey}.@each.${propertyKey}`, function () {
const parent = get(this, dependentKey);
if (!isArray(parent)) {
return A();
}
const callback =
arguments.length === 2
? (item) => !get(item, propertyKey)
: (item) => get(item, propertyKey) !== value;
return A(parent.filter(callback));
});
}