Revision Tracking
Gotchas and Techinques
Chris Hewell Garrett
Sr. Software Engineer, Client Application Frameworks
aka: "Autotracking"
@tracked
class PersonCard extends Component {
firstName = 'Liz';
lastName = 'Hewell';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateName(first, last) {
set(this, 'firstName', first);
set(this, 'lastName', last);
}
}
class PersonCard extends Component {
@tracked firstName = 'Liz';
@tracked lastName = 'Hewell';
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateName(first, last) {
this.firstName = first;
this.lastName = last;
}
}
class PersonCard extends Component {
firstName = 'Liz';
lastName = 'Hewell';
@computed('firstName', 'lastName')
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
@action
updateName(first, last) {
set(this, 'firstName', first);
set(this, 'lastName', last);
}
}
Gotchas
2
3
4
Short-circuiting
Infinite invalidation
Weak caching
1
Uncached getters
class Person {
firstName = 'Liz';
lastName = 'Fizz';
@computed('firstName', 'lastName')
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
class Person {
firstName = 'Liz';
lastName = 'Fizz';
@computed('firstName', 'lastName')
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
let liz = new Person();
liz.fullName; // 'calculating fullName...' logged
class Person {
firstName = 'Liz';
lastName = 'Fizz';
@computed('firstName', 'lastName')
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
let liz = new Person();
liz.fullName; // 'calculating fullName...' logged
liz.fullName; // nothing logged
class Person {
@tracked firstName = 'Liz';
@tracked lastName = 'Fizz';
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
let liz = new Person();
liz.fullName; // 'calculating fullName...' logged
liz.fullName; // nothing logged?
class Person {
@tracked firstName = 'Liz';
@tracked lastName = 'Fizz';
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
let liz = new Person();
liz.fullName; // 'calculating fullName...' logged
liz.fullName; // 'calculating fullName...' logged again!
class Person {
firstName = 'Liz';
lastName = 'Fizz';
@computed('firstName', 'lastName')
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
class MyComponent extends Component {
@computed('firstName', 'lastName')
get person() {
return new Person(this.firstName, this.lastName);
}
}
class MyComponent extends Component {
@tracked firstName;
@tracked lastName;
get person() {
return new Person(this.firstName, this.lastName);
}
}
class MyComponent extends Component {
@tracked firstName;
@tracked lastName;
get person() {
return new Person(this.firstName, this.lastName);
}
get fullName() {
return this.person.fullName;
}
}
import { cached } from 'tracked-toolbox';
class Person {
@tracked firstName = 'Liz';
@tracked lastName = 'Fizz';
@cached
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
import { cached } from 'tracked-toolbox';
class Person {
@tracked firstName = 'Liz';
@tracked lastName = 'Fizz';
@cached
get fullName() {
console.log('calculating fullName...')
return `${this.firstName} ${this.lastName}`;
}
}
let liz = new Person();
liz.fullName; // 'calculating fullName...' logged
liz.fullName; // nothing logged!
Gotchas
2
3
4
Short-circuiting
Infinite invalidation
Weak caching
1
Uncached getters
class MyComponent extends Component {
inViewport = false;
containerWidth = 1000;
width = 800;
@computed('inViewport', 'containerWidth', 'width')
get shouldShow() {
console.log('calculating shouldShow...');
if (this.inViewport) {
return this.containerWidth > this.width;
}
return false;
}
}
class MyComponent extends Component {
@tracked inViewport = false;
@tracked containerWidth = 1000;
@tracked width = 800;
get shouldShow() {
console.log('calculating shouldShow...');
if (this.inViewport) {
return this.containerWidth > this.width;
}
return false
}
}
Gotchas
2
3
4
Short-circuiting
Infinite invalidation
Weak caching
1
Uncached getters
class MyComponent extends Component {
url = 'http://example.com';
@computed('url')
get fetchedValue() {
return PromiseProxy.create({
promise: fetch(this.url),
});
}
}
class MyComponent extends Component {
@tracked url = 'http://example.com';
get fetchedValue() {
return PromiseProxy.create({
promise: fetch(this.url),
});
}
}
class MyComponent extends Component {
@tracked url = 'http://example.com';
get fetchedValue() {
return PromiseProxy.create({
promise: fetch(this.url),
});
}
}
class PromiseProxy extends Proxy {
constructor() {
super(...arguments);
// entangled content, for some reason
get(this, 'content');
this.promise.then((result) => {
set(this, 'content', result);
});
}
}
Gotchas
2
3
4
Short-circuiting
Infinite invalidation
Weak caching
1
Uncached getters
class MyComponent extends Component {
@tracked url = 'http://example.com';
@cached
get fetchedValue() {
return PromiseProxy.create({
promise: fetch(this.url),
});
}
}
class MyComponent extends Component {
@tracked baseUrl = 'http://example.com/search';
@tracked foo = true;
@tracked bar = false;
get searchUrl() {
let fooOrBar = this.foo || this.bar;
return `${this.baseUrl}?fooOrBar=${fooOrBar}`;
}
@cached
get searchResults() {
return PromiseProxy.create({
promise: fetch(this.searchUrl),
});
}
}
class MyComponent extends Component {
@tracked baseUrl = 'http://example.com/search';
@tracked foo = true;
@tracked bar = false;
get searchUrl() {
let fooOrBar = this.foo || this.bar;
return `${this.baseUrl}?fooOrBar=${fooOrBar}`;
}
_lastSearchUrl;
_lastSearchResults;
get searchResults() {
let { searchUrl } = this;
if (this._lastSearchUrl === searchUrl) {
this._lastSearchUrl = searchUrl;
this._lastSearchRseults = PromiseProxy.create({
promise: fetch(searchUrl),
});
}
return this._lastSearchResults;
}
}
Gotchas
2
3
4
Short-circuiting
Infinite invalidation
Weak caching
1
Uncached getters
Techniques
2
3
Tracking function calls
Libraries
1
Tracking dynamic keys
4
Tracking helpers and modifiers
class Store extends Component {
groceries = [
{ name: 'Milk', price: 1.23, perUnitPrice: 1.01, },
{ name: 'Eggs', price: 3.00, perUnitPrice: 0.25, },
{ name: 'Cheese', price: 1.34, perUnitPrice: 1.00 },
];
filterAmount = 2.00;
@computed('filterAmount', 'groceries.@each.price')
get filteredGroceries() {
let { filterAmount } = this;
return this.groceries.filter((item) => {
return item.price < filterAmount;
});
}
}
class Store extends Component {
groceries = [
{ name: 'Milk', price: 1.23, perUnitPrice: 1.01, },
{ name: 'Eggs', price: 3.00, perUnitPrice: 0.25, },
{ name: 'Cheese', price: 1.34, perUnitPrice: 1.00 },
];
filterAmount = 2.00;
filterKey = 'price';
@computed('filterAmount', 'filterKey', 'groceries.@each.???')
get filteredGroceries() {
let { filterAmount, filterKey } = this;
return this.groceries.filter((item) => {
return item[filterKey] < filterAmount;
});
}
}
ember-macro-helpers
createClassComputed()
class GroceryItem {
@tracked name;
@tracked price;
@tracked perUnitPrice;
constructor(name, price, perUnitPrice) {
this.name = name;
this.price = price;
this.perUnitPrice = perUnitPrice;
}
}
class Store extends Component {
groceries = [
new GroceryItem('Milk', 1.23, 1.01),
new GroceryItem('Eggs', 3.00, 0.25),
new GroceryItem('Cheese', 1.34, 1.00),
];
@tracked filterAmount = 2.00;
@tracked filterKey = 'price';
get filteredGroceries() {
let { filterAmount, filterKey } = this;
return this.groceries.filter((item) => {
return item[filterKey] < filterAmount;
});
}
}
Techniques
2
3
Tracking function calls
Libraries
1
Tracking dynamic keys
4
Tracking helpers and modifiers
class Store extends Component {
groceries = [
new GroceryItem('Milk', 1.23, 1.01),
new GroceryItem('Eggs', 3.00, 0.25),
new GroceryItem('Cheese', 1.34, 1.00),
];
@tracked priceFilter = 2.00;
@tracked perUnitPriceFilter = 1.00;
filter(filterKey, filterAmount) {
return this.groceries.filter((item) => {
return item[filterKey] < filterAmount;
});
}
get filteredByPrice() {
return this.filter('price', this.priceFilter);
}
get filteredByPerUnitPrice() {
return this.filter('perUnitPrice', this.perUnitPriceFilter);
}
}
Techniques
2
3
Tracking function calls
Libraries
1
Tracking dynamic keys
4
Tracking helpers and modifiers
import { observer } from '@ember/object';
import { inject as service } from '@ember/service';
import Helper from '@ember/component/helper';
export default Helper.extend({
i18n: service(),
compute() {
return this.i18n.isRtlLanguage;
},
_recomputeOnRtlChange: observer('i18n.isRtlLanguage', function () {
this.recompute();
}),
});
import { inject as service } from '@ember/service';
import Helper from '@ember/component/helper';
export default Helper.extend({
i18n: service(),
compute() {
return this.i18n.isRtlLanguage;
},
});
import Modifier from 'ember-modifier'
export default class ScrollModifier extends Modifier {
@service scrollTop;
didInstall() {
this.element.scrollTop = this.scrollTop.value;
}
didUpdateAttrs() {
this.element.ScrollTop = this.scrollTop.value;
}
}
Techniques
2
3
Tracking function calls
Libraries
1
Tracking dynamic keys
4
Tracking helpers and modifiers
- tracked-built-ins
- Arrays, objects, maps, and sets
- tracked-maps-and-sets
- tracked-toolbox
- @cached
- @dedupeTracked
- @localCopy
- tracked-redux
Thank you!
Autotracking Gotchas and Techniques
By pzuraq
Autotracking Gotchas and Techniques
- 957