Skip to content

Commit

Permalink
Document the caching of autotracked properties
Browse files Browse the repository at this point in the history
  • Loading branch information
YoranBrondsema committed Aug 8, 2020
1 parent a15d694 commit 52035ac
Show file tree
Hide file tree
Showing 6 changed files with 521 additions and 5 deletions.
86 changes: 86 additions & 0 deletions guides/release/in-depth-topics/autotracking-in-depth.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,4 +321,90 @@ class ShoppingList {
}
```

## Caching of tracked properties

In contrast to computed properties from pre-Octane, tracked properties are not
cached. A tracked property can also be recomputed even though its dependencies
haven't changed. The following example shows this behavior:

```js
import { tracked } from '@glimmer/tracking';

let count = 0;

class Photo {
@tracked width = 600;
@tracked height = 400;

get aspectRatio() {
count++;
return this.width / this.height;
}
}

let photo = new Photo();

console.log(photo.aspectRatio); // 1.5
console.log(count); // 1
console.log(photo.aspectRatio); // 1.5
console.log(count); // 2

photo.width = 800;

console.log(photo.aspectRatio); // 2
console.log(count); // 3
```

From the value of `count`, we see that `aspectRatio` was calculated 3 times.

Recomputing is fine in most cases. If the computation that happens in the
getter is very expensive, however, you will want to cache the value and
retrieve it when the dependencies haven't changed. You want to recompute only
if a dependency has been updated.

Ember's [cache API](https://github.com/ember-polyfills/ember-cache-primitive-polyfill) lets
you cache a getter in 2 steps:

1. Pass a function that is costly to compute to `createCache`.
1. In the getter, call the function with `getValue` and return its value.

With these steps in mind, let's introduce caching to `aspectRatio`:

```js
import { tracked } from '@glimmer/tracking';
import { createCache, getValue } from '@glimmer/tracking/primitives/cache';

let count = 0;

class Photo {
@tracked width = 600;
@tracked height = 400;

// `#` indicates a private field on the class
#aspectRatioCache = createCache(() => {
count++;
return this.width / this.height;
});

get aspectRatio() {
return getValue(this.#aspectRatioCache);
}
}

let photo = new Photo();

console.log(photo.aspectRatio); // 1.5
console.log(count); // 1
console.log(photo.aspectRatio); // 1.5
console.log(count); // 1

photo.width = 800;

console.log(photo.aspectRatio); // 2
console.log(count); // 2
```

From the value of `count`, we see that, this time, `aspectRatio` was calculated
only twice.

<!-- eof - needed for pages that end in a code block -->
90 changes: 88 additions & 2 deletions guides/v3.15.0/in-depth-topics/autotracking-in-depth.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default class HelloComponent extends Component {
constructor() {
super(...arguments);

fetch('./api/preferences')
fetch('/api/preferences')
.then(r => r.json()) // convert the response to a JS object
.then(response => {
this.language = response.preferredLanguage;
Expand Down Expand Up @@ -262,7 +262,7 @@ export default class ApplicationController extends Controller {
```handlebars {data-filename=app/templates/application.hbs}
{{@model.fullName}}
<button {{on "click" (fn this.updateName 'Krati' 'Ahuja')}}>
<button type="button" {{on "click" (fn this.updateName 'Krati' 'Ahuja')}}>
Update Name
</button>
```
Expand Down Expand Up @@ -321,4 +321,90 @@ class ShoppingList {
}
```

## Caching of tracked properties

In contrast to computed properties from pre-Octane, tracked properties are not
cached. A tracked property can also be recomputed even though its dependencies
haven't changed. The following example shows this behavior:

```js
import { tracked } from '@glimmer/tracking';

let count = 0;

class Photo {
@tracked width = 600;
@tracked height = 400;

get aspectRatio() {
count++;
return this.width / this.height;
}
}

let photo = new Photo();

console.log(photo.aspectRatio); // 1.5
console.log(count); // 1
console.log(photo.aspectRatio); // 1.5
console.log(count); // 2

photo.width = 800;

console.log(photo.aspectRatio); // 2
console.log(count); // 3
```

From the value of `count`, we see that `aspectRatio` was calculated 3 times.

Recomputing is fine in most cases. If the computation that happens in the
getter is very expensive, however, you will want to cache the value and
retrieve it when the dependencies haven't changed. You want to recompute only
if a dependency has been updated.

Ember's [cache API](https://github.com/ember-polyfills/ember-cache-primitive-polyfill) lets
you cache a getter in 2 steps:

1. Pass a function that is costly to compute to `createCache`.
1. In the getter, call the function with `getValue` and return its value.

With these steps in mind, let's introduce caching to `aspectRatio`:

```js
import { tracked } from '@glimmer/tracking';
import { createCache, getValue } from '@glimmer/tracking/primitives/cache';

let count = 0;

class Photo {
@tracked width = 600;
@tracked height = 400;

// `#` indicates a private field on the class
#aspectRatioCache = createCache(() => {
count++;
return this.width / this.height;
});

get aspectRatio() {
return getValue(this.#aspectRatioCache);
}
}

let photo = new Photo();

console.log(photo.aspectRatio); // 1.5
console.log(count); // 1
console.log(photo.aspectRatio); // 1.5
console.log(count); // 1

photo.width = 800;

console.log(photo.aspectRatio); // 2
console.log(count); // 2
```

From the value of `count`, we see that, this time, `aspectRatio` was calculated
only twice.

<!-- eof - needed for pages that end in a code block -->
90 changes: 88 additions & 2 deletions guides/v3.16.0/in-depth-topics/autotracking-in-depth.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export default class HelloComponent extends Component {
constructor() {
super(...arguments);

fetch('./api/preferences')
fetch('/api/preferences')
.then(r => r.json()) // convert the response to a JS object
.then(response => {
this.language = response.preferredLanguage;
Expand Down Expand Up @@ -262,7 +262,7 @@ export default class ApplicationController extends Controller {
```handlebars {data-filename=app/templates/application.hbs}
{{@model.fullName}}
<button {{on "click" (fn this.updateName 'Krati' 'Ahuja')}}>
<button type="button" {{on "click" (fn this.updateName 'Krati' 'Ahuja')}}>
Update Name
</button>
```
Expand Down Expand Up @@ -321,4 +321,90 @@ class ShoppingList {
}
```

## Caching of tracked properties

In contrast to computed properties from pre-Octane, tracked properties are not
cached. A tracked property can also be recomputed even though its dependencies
haven't changed. The following example shows this behavior:

```js
import { tracked } from '@glimmer/tracking';

let count = 0;

class Photo {
@tracked width = 600;
@tracked height = 400;

get aspectRatio() {
count++;
return this.width / this.height;
}
}

let photo = new Photo();

console.log(photo.aspectRatio); // 1.5
console.log(count); // 1
console.log(photo.aspectRatio); // 1.5
console.log(count); // 2

photo.width = 800;

console.log(photo.aspectRatio); // 2
console.log(count); // 3
```

From the value of `count`, we see that `aspectRatio` was calculated 3 times.

Recomputing is fine in most cases. If the computation that happens in the
getter is very expensive, however, you will want to cache the value and
retrieve it when the dependencies haven't changed. You want to recompute only
if a dependency has been updated.

Ember's [cache API](https://github.com/ember-polyfills/ember-cache-primitive-polyfill) lets
you cache a getter in 2 steps:

1. Pass a function that is costly to compute to `createCache`.
1. In the getter, call the function with `getValue` and return its value.

With these steps in mind, let's introduce caching to `aspectRatio`:

```js
import { tracked } from '@glimmer/tracking';
import { createCache, getValue } from '@glimmer/tracking/primitives/cache';

let count = 0;

class Photo {
@tracked width = 600;
@tracked height = 400;

// `#` indicates a private field on the class
#aspectRatioCache = createCache(() => {
count++;
return this.width / this.height;
});

get aspectRatio() {
return getValue(this.#aspectRatioCache);
}
}

let photo = new Photo();

console.log(photo.aspectRatio); // 1.5
console.log(count); // 1
console.log(photo.aspectRatio); // 1.5
console.log(count); // 1

photo.width = 800;

console.log(photo.aspectRatio); // 2
console.log(count); // 2
```

From the value of `count`, we see that, this time, `aspectRatio` was calculated
only twice.

<!-- eof - needed for pages that end in a code block -->
Loading

0 comments on commit 52035ac

Please sign in to comment.