ember-source
Version:
A JavaScript framework for creating ambitious web applications
132 lines (110 loc) • 3.72 kB
JavaScript
import { meta } from '../@ember/-internals/meta/lib/meta.js';
import { isEmberArray } from '../@ember/array/-internals.js';
import '../@ember/-internals/environment/index.js';
import { trackedData, dirtyTagFor, consumeTag, tagFor } from '../@glimmer/validator/index.js';
import { S as SELF_TAG, C as CHAIN_PASS_THROUGH } from './observers-R1ZklwWy.js';
import { i as isElementDescriptor, s as setClassicDecorator, C as COMPUTED_SETTERS } from './decorator-BdDDBUd2.js';
/**
@decorator
@private
Marks a property as tracked.
By default, a component's properties are expected to be static,
meaning you are not able to update them and have the template update accordingly.
Marking a property as tracked means that when that property changes,
a rerender of the component is scheduled so the template is kept up to date.
There are two usages for the `@tracked` decorator, shown below.
@example No dependencies
If you don't pass an argument to `@tracked`, only changes to that property
will be tracked:
```typescript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class MyComponent extends Component {
@tracked
remainingApples = 10
}
```
When something changes the component's `remainingApples` property, the rerender
will be scheduled.
@example Dependents
In the case that you have a computed property that depends other
properties, you want to track both so that when one of the
dependents change, a rerender is scheduled.
In the following example we have two properties,
`eatenApples`, and `remainingApples`.
```typescript
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
const totalApples = 100;
export default class MyComponent extends Component {
@tracked
eatenApples = 0
get remainingApples() {
return totalApples - this.eatenApples;
}
increment() {
this.eatenApples = this.eatenApples + 1;
}
}
```
@param dependencies Optional dependents to be tracked.
*/
function tracked(...args) {
if (!isElementDescriptor(args)) {
let propertyDesc = args[0];
let initializer = propertyDesc ? propertyDesc.initializer : undefined;
let value = propertyDesc ? propertyDesc.value : undefined;
let decorator = function (target, key, _desc, _meta, isClassicDecorator) {
let fieldDesc = {
initializer: initializer || (() => value)
};
return descriptorForField([target, key, fieldDesc]);
};
setClassicDecorator(decorator);
return decorator;
}
return descriptorForField(args);
}
function descriptorForField([target, key, desc]) {
let {
getter,
setter
} = trackedData(key, desc ? desc.initializer : undefined);
function get() {
let value = getter(this);
// Add the tag of the returned value if it is an array, since arrays
// should always cause updates if they are consumed and then changed
if (Array.isArray(value) || isEmberArray(value)) {
consumeTag(tagFor(value, '[]'));
}
return value;
}
function set(newValue) {
setter(this, newValue);
dirtyTagFor(this, SELF_TAG);
}
let newDesc = {
enumerable: true,
configurable: true,
isTracked: true,
get,
set
};
COMPUTED_SETTERS.add(set);
meta(target).writeDescriptors(key, new TrackedDescriptor(get, set));
return newDesc;
}
class TrackedDescriptor {
constructor(_get, _set) {
this._get = _get;
this._set = _set;
CHAIN_PASS_THROUGH.add(this);
}
get(obj) {
return this._get.call(obj);
}
set(obj, _key, value) {
this._set.call(obj, value);
}
}
export { TrackedDescriptor as T, tracked as t };