Skip to content

Commit 1a247e0

Browse files
authoredFeb 19, 2019
Merge pull request #17634 from pzuraq/add-autotrack-to-templates
[FEAT] Adds autotracked to the the Glimmer references system
2 parents 68922f3 + 164cbb3 commit 1a247e0

File tree

3 files changed

+508
-18
lines changed

3 files changed

+508
-18
lines changed
 

‎packages/@ember/-internals/glimmer/lib/utils/references.ts

+66-18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1-
import { didRender, get, set, tagFor, tagForProperty, watchKey } from '@ember/-internals/metal';
1+
import {
2+
didRender,
3+
get,
4+
getCurrentTracker,
5+
set,
6+
setCurrentTracker,
7+
tagFor,
8+
tagForProperty,
9+
watchKey,
10+
} from '@ember/-internals/metal';
211
import { isProxy, symbol } from '@ember/-internals/utils';
12+
import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features';
313
import { DEBUG } from '@glimmer/env';
414
import { Opaque } from '@glimmer/interfaces';
515
import {
@@ -178,21 +188,24 @@ export class RootPropertyReference extends PropertyReference
178188
public tag: Tag;
179189
private _parentValue: any;
180190
private _propertyKey: string;
191+
private _propertyTag: TagWrapper<UpdatableTag>;
181192

182193
constructor(parentValue: any, propertyKey: string) {
183194
super();
184195

185196
this._parentValue = parentValue;
186197
this._propertyKey = propertyKey;
187198

199+
if (EMBER_METAL_TRACKED_PROPERTIES) {
200+
this._propertyTag = UpdatableTag.create(CONSTANT_TAG);
201+
} else {
202+
this._propertyTag = UpdatableTag.create(tagForProperty(parentValue, propertyKey));
203+
}
204+
188205
if (DEBUG) {
189-
this.tag = TwoWayFlushDetectionTag.create(
190-
tagForProperty(parentValue, propertyKey),
191-
propertyKey,
192-
this
193-
);
206+
this.tag = TwoWayFlushDetectionTag.create(this._propertyTag, propertyKey, this);
194207
} else {
195-
this.tag = tagForProperty(parentValue, propertyKey);
208+
this.tag = this._propertyTag;
196209
}
197210

198211
if (DEBUG) {
@@ -207,7 +220,25 @@ export class RootPropertyReference extends PropertyReference
207220
(this.tag.inner as TwoWayFlushDetectionTag).didCompute(_parentValue);
208221
}
209222

210-
return get(_parentValue, _propertyKey);
223+
let parent: any;
224+
let tracker: any;
225+
226+
if (EMBER_METAL_TRACKED_PROPERTIES) {
227+
parent = getCurrentTracker();
228+
tracker = setCurrentTracker();
229+
}
230+
231+
let ret = get(_parentValue, _propertyKey);
232+
233+
if (EMBER_METAL_TRACKED_PROPERTIES) {
234+
setCurrentTracker(parent!);
235+
let tag = tracker!.combine();
236+
if (parent) parent.add(tag);
237+
238+
this._propertyTag.inner.update(tag);
239+
}
240+
241+
return ret;
211242
}
212243

213244
[UPDATE](value: any) {
@@ -218,34 +249,31 @@ export class RootPropertyReference extends PropertyReference
218249
export class NestedPropertyReference extends PropertyReference {
219250
public tag: Tag;
220251
private _parentReference: any;
221-
private _parentObjectTag: TagWrapper<UpdatableTag>;
252+
private _propertyTag: TagWrapper<UpdatableTag>;
222253
private _propertyKey: string;
223254

224255
constructor(parentReference: VersionedPathReference<Opaque>, propertyKey: string) {
225256
super();
226257

227258
let parentReferenceTag = parentReference.tag;
228-
let parentObjectTag = UpdatableTag.create(CONSTANT_TAG);
259+
let propertyTag = UpdatableTag.create(CONSTANT_TAG);
229260

230261
this._parentReference = parentReference;
231-
this._parentObjectTag = parentObjectTag;
262+
this._propertyTag = propertyTag;
232263
this._propertyKey = propertyKey;
233264

234265
if (DEBUG) {
235-
let tag = combine([parentReferenceTag, parentObjectTag]);
266+
let tag = combine([parentReferenceTag, propertyTag]);
236267
this.tag = TwoWayFlushDetectionTag.create(tag, propertyKey, this);
237268
} else {
238-
this.tag = combine([parentReferenceTag, parentObjectTag]);
269+
this.tag = combine([parentReferenceTag, propertyTag]);
239270
}
240271
}
241272

242273
compute() {
243-
let { _parentReference, _parentObjectTag, _propertyKey } = this;
274+
let { _parentReference, _propertyTag, _propertyKey } = this;
244275

245276
let parentValue = _parentReference.value();
246-
247-
_parentObjectTag.inner.update(tagForProperty(parentValue, _propertyKey));
248-
249277
let parentValueType = typeof parentValue;
250278

251279
if (parentValueType === 'string' && _propertyKey === 'length') {
@@ -261,7 +289,27 @@ export class NestedPropertyReference extends PropertyReference {
261289
(this.tag.inner as TwoWayFlushDetectionTag).didCompute(parentValue);
262290
}
263291

264-
return get(parentValue, _propertyKey);
292+
let parent: any;
293+
let tracker: any;
294+
295+
if (EMBER_METAL_TRACKED_PROPERTIES) {
296+
parent = getCurrentTracker();
297+
tracker = setCurrentTracker();
298+
}
299+
300+
let ret = get(parentValue, _propertyKey);
301+
302+
if (EMBER_METAL_TRACKED_PROPERTIES) {
303+
setCurrentTracker(parent!);
304+
let tag = tracker!.combine();
305+
if (parent) parent.add(tag);
306+
307+
_propertyTag.inner.update(tag);
308+
} else {
309+
_propertyTag.inner.update(tagForProperty(parentValue, _propertyKey));
310+
}
311+
312+
return ret;
265313
} else {
266314
return undefined;
267315
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features';
2+
import { Object as EmberObject } from '@ember/-internals/runtime';
3+
import { tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal';
4+
import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers';
5+
6+
import { Component } from '../../utils/helpers';
7+
8+
if (EMBER_METAL_TRACKED_PROPERTIES) {
9+
moduleFor(
10+
'Component Tracked Properties',
11+
class extends RenderingTestCase {
12+
'@test tracked properties rerender when updated'() {
13+
let CountComponent = Component.extend({
14+
count: tracked({ value: 0 }),
15+
16+
increment() {
17+
this.count++;
18+
},
19+
});
20+
21+
this.registerComponent('counter', {
22+
ComponentClass: CountComponent,
23+
template: '<button {{action this.increment}}>{{this.count}}</button>',
24+
});
25+
26+
this.render('<Counter />');
27+
28+
this.assertText('0');
29+
30+
runTask(() => this.$('button').click());
31+
32+
this.assertText('1');
33+
}
34+
35+
'@test nested tracked properties rerender when updated'() {
36+
let Counter = EmberObject.extend({
37+
count: tracked({ value: 0 }),
38+
});
39+
40+
let CountComponent = Component.extend({
41+
counter: Counter.create(),
42+
43+
increment() {
44+
this.counter.count++;
45+
},
46+
});
47+
48+
this.registerComponent('counter', {
49+
ComponentClass: CountComponent,
50+
template: '<button {{action this.increment}}>{{this.counter.count}}</button>',
51+
});
52+
53+
this.render('<Counter />');
54+
55+
this.assertText('0');
56+
57+
runTask(() => this.$('button').click());
58+
59+
this.assertText('1');
60+
}
61+
62+
'@test getters update when dependent properties are invalidated'() {
63+
let CountComponent = Component.extend({
64+
count: tracked({ value: 0 }),
65+
66+
countAlias: descriptor({
67+
get() {
68+
return this.count;
69+
},
70+
}),
71+
72+
increment() {
73+
this.count++;
74+
},
75+
});
76+
77+
this.registerComponent('counter', {
78+
ComponentClass: CountComponent,
79+
template: '<button {{action this.increment}}>{{this.countAlias}}</button>',
80+
});
81+
82+
this.render('<Counter />');
83+
84+
this.assertText('0');
85+
86+
runTask(() => this.$('button').click());
87+
88+
this.assertText('1');
89+
}
90+
91+
'@test nested getters update when dependent properties are invalidated'() {
92+
let Counter = EmberObject.extend({
93+
count: tracked({ value: 0 }),
94+
95+
countAlias: descriptor({
96+
get() {
97+
return this.count;
98+
},
99+
}),
100+
});
101+
102+
let CountComponent = Component.extend({
103+
counter: Counter.create(),
104+
105+
increment() {
106+
this.counter.count++;
107+
},
108+
});
109+
110+
this.registerComponent('counter', {
111+
ComponentClass: CountComponent,
112+
template: '<button {{action this.increment}}>{{this.counter.countAlias}}</button>',
113+
});
114+
115+
this.render('<Counter />');
116+
117+
this.assertText('0');
118+
119+
runTask(() => this.$('button').click());
120+
121+
this.assertText('1');
122+
}
123+
124+
'@test tracked object passed down through components updates correctly'(assert) {
125+
let Person = EmberObject.extend({
126+
first: tracked({ value: 'Rob' }),
127+
last: tracked({ value: 'Jackson' }),
128+
129+
full: descriptor({
130+
get() {
131+
return `${this.first} ${this.last}`;
132+
},
133+
}),
134+
});
135+
136+
let ParentComponent = Component.extend({
137+
person: Person.create(),
138+
});
139+
140+
let ChildComponent = Component.extend({
141+
updatePerson() {
142+
this.person.first = 'Kris';
143+
this.person.last = 'Selden';
144+
},
145+
});
146+
147+
this.registerComponent('parent', {
148+
ComponentClass: ParentComponent,
149+
template: strip`
150+
<div id="parent">{{this.person.full}}</div>
151+
<Child @person={{this.person}}/>
152+
`,
153+
});
154+
155+
this.registerComponent('child', {
156+
ComponentClass: ChildComponent,
157+
template: strip`
158+
<div id="child">{{this.person.full}}</div>
159+
<button onclick={{action this.updatePerson}}></button>
160+
`,
161+
});
162+
163+
this.render('<Parent />');
164+
165+
assert.equal(this.$('#parent').text(), 'Rob Jackson');
166+
assert.equal(this.$('#child').text(), 'Rob Jackson');
167+
168+
runTask(() => this.$('button').click());
169+
170+
assert.equal(this.$('#parent').text(), 'Kris Selden');
171+
assert.equal(this.$('#child').text(), 'Kris Selden');
172+
}
173+
174+
'@test yielded getters update correctly'() {
175+
let PersonComponent = Component.extend({
176+
first: tracked({ value: 'Rob' }),
177+
last: tracked({ value: 'Jackson' }),
178+
179+
full: descriptor({
180+
get() {
181+
return `${this.first} ${this.last}`;
182+
},
183+
}),
184+
185+
updatePerson() {
186+
this.first = 'Kris';
187+
this.last = 'Selden';
188+
},
189+
});
190+
191+
this.registerComponent('person', {
192+
ComponentClass: PersonComponent,
193+
template: strip`
194+
{{yield this.full (action this.updatePerson)}}
195+
`,
196+
});
197+
198+
this.render(strip`
199+
<Person as |name update|>
200+
<button onclick={{update}}>
201+
{{name}}
202+
</button>
203+
</Person>
204+
`);
205+
206+
this.assertText('Rob Jackson');
207+
208+
runTask(() => this.$('button').click());
209+
210+
this.assertText('Kris Selden');
211+
}
212+
213+
'@test yielded nested getters update correctly'() {
214+
let Person = EmberObject.extend({
215+
first: tracked({ value: 'Rob' }),
216+
last: tracked({ value: 'Jackson' }),
217+
218+
full: descriptor({
219+
get() {
220+
return `${this.first} ${this.last}`;
221+
},
222+
}),
223+
});
224+
225+
let PersonComponent = Component.extend({
226+
person: Person.create(),
227+
228+
updatePerson() {
229+
this.person.first = 'Kris';
230+
this.person.last = 'Selden';
231+
},
232+
});
233+
234+
this.registerComponent('person', {
235+
ComponentClass: PersonComponent,
236+
template: strip`
237+
{{yield this.person (action this.updatePerson)}}
238+
`,
239+
});
240+
241+
this.render(strip`
242+
<Person as |p update|>
243+
<button onclick={{update}}>
244+
{{p.full}}
245+
</button>
246+
</Person>
247+
`);
248+
249+
this.assertText('Rob Jackson');
250+
251+
runTask(() => this.$('button').click());
252+
253+
this.assertText('Kris Selden');
254+
}
255+
}
256+
);
257+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import { EMBER_METAL_TRACKED_PROPERTIES } from '@ember/canary-features';
2+
import { Object as EmberObject } from '@ember/-internals/runtime';
3+
import { tracked, nativeDescDecorator as descriptor } from '@ember/-internals/metal';
4+
import { moduleFor, RenderingTestCase, strip, runTask } from 'internal-test-helpers';
5+
6+
import { Component } from '../../utils/helpers';
7+
8+
if (EMBER_METAL_TRACKED_PROPERTIES) {
9+
moduleFor(
10+
'Helper Tracked Properties',
11+
class extends RenderingTestCase {
12+
'@test tracked properties rerender when updated'(assert) {
13+
let computeCount = 0;
14+
15+
let PersonComponent = Component.extend({
16+
name: tracked({ value: 'bob' }),
17+
18+
updateName() {
19+
this.name = 'sal';
20+
},
21+
});
22+
23+
this.registerComponent('person', {
24+
ComponentClass: PersonComponent,
25+
template: strip`
26+
<button onclick={{action this.updateName}}>
27+
{{hello-world this.name}}
28+
</button>
29+
`,
30+
});
31+
32+
this.registerHelper('hello-world', ([value]) => {
33+
computeCount++;
34+
return `${value}-value`;
35+
});
36+
37+
this.render('<Person/>');
38+
39+
this.assertText('bob-value');
40+
41+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
42+
43+
runTask(() => this.rerender());
44+
45+
this.assertText('bob-value');
46+
47+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
48+
49+
runTask(() => this.$('button').click());
50+
51+
this.assertText('sal-value');
52+
53+
assert.strictEqual(computeCount, 2, 'compute is called exactly 2 times');
54+
}
55+
56+
'@test nested tracked properties rerender when updated'(assert) {
57+
let computeCount = 0;
58+
59+
let Person = EmberObject.extend({
60+
name: tracked({ value: 'bob' }),
61+
});
62+
63+
this.registerHelper('hello-world', ([value]) => {
64+
computeCount++;
65+
return `${value}-value`;
66+
});
67+
68+
this.render('{{hello-world model.name}}', {
69+
model: Person.create(),
70+
});
71+
72+
this.assertText('bob-value');
73+
74+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
75+
76+
runTask(() => this.rerender());
77+
78+
this.assertText('bob-value');
79+
80+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
81+
82+
runTask(() => (this.context.model.name = 'sal'));
83+
84+
this.assertText('sal-value');
85+
86+
assert.strictEqual(computeCount, 2, 'compute is called exactly 2 times');
87+
}
88+
89+
'@test getters update when dependent properties are invalidated'(assert) {
90+
let computeCount = 0;
91+
92+
let PersonComponent = Component.extend({
93+
first: tracked({ value: 'Rob' }),
94+
last: tracked({ value: 'Jackson' }),
95+
96+
full: descriptor({
97+
get() {
98+
return `${this.first} ${this.last}`;
99+
},
100+
}),
101+
102+
updatePerson() {
103+
this.first = 'Kris';
104+
this.last = 'Selden';
105+
},
106+
});
107+
108+
this.registerComponent('person', {
109+
ComponentClass: PersonComponent,
110+
template: strip`
111+
<button onclick={{action this.updatePerson}}>
112+
{{hello-world this.full}}
113+
</button>
114+
`,
115+
});
116+
117+
this.registerHelper('hello-world', ([value]) => {
118+
computeCount++;
119+
return value;
120+
});
121+
122+
this.render('<Person/>');
123+
124+
this.assertText('Rob Jackson');
125+
126+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
127+
128+
runTask(() => this.rerender());
129+
130+
this.assertText('Rob Jackson');
131+
132+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
133+
134+
runTask(() => this.$('button').click());
135+
136+
this.assertText('Kris Selden');
137+
138+
assert.strictEqual(computeCount, 2, 'compute is called exactly 2 times');
139+
}
140+
141+
'@test nested getters update when dependent properties are invalidated'(assert) {
142+
let computeCount = 0;
143+
144+
let Person = EmberObject.extend({
145+
first: tracked({ value: 'Rob' }),
146+
last: tracked({ value: 'Jackson' }),
147+
148+
full: descriptor({
149+
get() {
150+
return `${this.first} ${this.last}`;
151+
},
152+
}),
153+
});
154+
155+
this.registerHelper('hello-world', ([value]) => {
156+
computeCount++;
157+
return value;
158+
});
159+
160+
this.render('{{hello-world model.full}}', {
161+
model: Person.create(),
162+
});
163+
164+
this.assertText('Rob Jackson');
165+
166+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
167+
168+
runTask(() => this.rerender());
169+
170+
this.assertText('Rob Jackson');
171+
172+
assert.strictEqual(computeCount, 1, 'compute is called exactly 1 time');
173+
174+
runTask(() => {
175+
this.context.model.first = 'Kris';
176+
this.context.model.last = 'Selden';
177+
});
178+
179+
this.assertText('Kris Selden');
180+
181+
assert.strictEqual(computeCount, 2, 'compute is called exactly 2 times');
182+
}
183+
}
184+
);
185+
}

0 commit comments

Comments
 (0)
Please sign in to comment.