Skip to content

Commit 21f24d1

Browse files
committed
feat(change_detection): implement hydration/dehydration
1 parent c1dc3cc commit 21f24d1

File tree

8 files changed

+154
-33
lines changed

8 files changed

+154
-33
lines changed

modules/angular2/src/change_detection/change_detection_jit_generator.es6

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import {
3030
* this.dispatcher = dispatcher;
3131
* this.protos = protos;
3232
*
33-
* this.context = null;
34-
* this.address0 = null;
35-
* this.city1 = null;
33+
* this.context = ChangeDetectionUtil.unitialized();
34+
* this.address0 = ChangeDetectionUtil.unitialized();
35+
* this.city1 = ChangeDetectionUtil.unitialized();
3636
* }
3737
* ChangeDetector0.prototype = Object.create(AbstractChangeDetector.prototype);
3838
*
@@ -70,10 +70,20 @@ import {
7070
* }
7171
*
7272
*
73-
* ChangeDetector0.prototype.setContext = function(context) {
73+
* ChangeDetector0.prototype.hydrate = function(context) {
7474
* this.context = context;
7575
* }
7676
*
77+
* ChangeDetector0.prototype.dehydrate = function(context) {
78+
* this.context = ChangeDetectionUtil.unitialized();
79+
* this.address0 = ChangeDetectionUtil.unitialized();
80+
* this.city1 = ChangeDetectionUtil.unitialized();
81+
* }
82+
*
83+
* ChangeDetector0.prototype.hydrated = function() {
84+
* return this.context !== ChangeDetectionUtil.unitialized();
85+
* }
86+
*
7787
* return ChangeDetector0;
7888
*
7989
*
@@ -119,11 +129,22 @@ ${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
119129
`;
120130
}
121131

122-
function setContextTemplate(type:string):string {
132+
function pipeOnDestroyTemplate(pipeNames:List) {
133+
return pipeNames.map((p) => `${p}.onDestroy()`).join("\n");
134+
}
135+
136+
function hydrateTemplate(type:string, fieldsDefinitions:string, pipeOnDestroy:string):string {
123137
return `
124-
${type}.prototype.setContext = function(context) {
138+
${type}.prototype.hydrate = function(context) {
125139
this.context = context;
126140
}
141+
${type}.prototype.dehydrate = function() {
142+
${pipeOnDestroy}
143+
${fieldsDefinitions}
144+
}
145+
${type}.prototype.hydrated = function() {
146+
return this.context !== ${UTIL}.unitialized();
147+
}
127148
`;
128149
}
129150

@@ -162,7 +183,10 @@ if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
162183
function pipeCheckTemplate(context:string, pipe:string, pipeType:string,
163184
value:string, change:string, addRecord:string, notify:string):string{
164185
return `
165-
if (${pipe} === ${UTIL}.unitialized() || !${pipe}.supports(${context})) {
186+
if (${pipe} === ${UTIL}.unitialized()) {
187+
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context});
188+
} else if (!${pipe}.supports(${context})) {
189+
${pipe}.onDestroy();
166190
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context});
167191
}
168192
@@ -281,25 +305,34 @@ export class ChangeDetectorJITGenerator {
281305
}
282306

283307
generate():Function {
284-
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext());
308+
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genHydrate());
285309
return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'ContextWithVariableBindings', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, ContextWithVariableBindings, this.records);
286310
}
287311

288312
genConstructor():string {
313+
return constructorTemplate(this.typeName, this.genFieldDefinitions());
314+
}
315+
316+
genHydrate():string {
317+
return hydrateTemplate(this.typeName, this.genFieldDefinitions(),
318+
pipeOnDestroyTemplate(this.getnonNullPipeNames()));
319+
}
320+
321+
genFieldDefinitions() {
289322
var fields = [];
290323
fields = fields.concat(this.fieldNames);
324+
fields = fields.concat(this.getnonNullPipeNames());
325+
return fieldDefinitionsTemplate(fields);
326+
}
291327

328+
getnonNullPipeNames():List<String> {
329+
var pipes = [];
292330
this.records.forEach((r) => {
293331
if (r.mode === RECORD_TYPE_PIPE) {
294-
fields.push(this.pipeNames[r.selfIndex]);
332+
pipes.push(this.pipeNames[r.selfIndex]);
295333
}
296334
});
297-
298-
return constructorTemplate(this.typeName, fieldDefinitionsTemplate(fields));
299-
}
300-
301-
genSetContext():string {
302-
return setContextTemplate(this.typeName);
335+
return pipes;
303336
}
304337

305338
genDetectChanges():string {

modules/angular2/src/change_detection/dynamic_change_detector.js

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,36 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
4343
this.prevContexts = ListWrapper.createFixedSize(protoRecords.length + 1);
4444
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
4545

46+
ListWrapper.fill(this.values, uninitialized);
47+
ListWrapper.fill(this.pipes, null);
48+
ListWrapper.fill(this.prevContexts, uninitialized);
49+
ListWrapper.fill(this.changes, false);
50+
4651
this.protos = protoRecords;
4752
}
4853

49-
setContext(context:any) {
54+
hydrate(context:any) {
55+
this.values[0] = context;
56+
}
57+
58+
dehydrate() {
59+
this._destroyPipes();
5060
ListWrapper.fill(this.values, uninitialized);
5161
ListWrapper.fill(this.changes, false);
5262
ListWrapper.fill(this.pipes, null);
5363
ListWrapper.fill(this.prevContexts, uninitialized);
54-
this.values[0] = context;
64+
}
65+
66+
_destroyPipes() {
67+
for(var i = 0; i < this.pipes.length; ++i) {
68+
if (isPresent(this.pipes[i])) {
69+
this.pipes[i].onDestroy();
70+
}
71+
}
72+
}
73+
74+
hydrated():boolean {
75+
return this.values[0] !== uninitialized;
5576
}
5677

5778
detectChangesInRecords(throwOnChange:boolean) {
@@ -184,11 +205,13 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
184205
var storedPipe = this._readPipe(proto);
185206
if (isPresent(storedPipe) && storedPipe.supports(context)) {
186207
return storedPipe;
187-
} else {
188-
var pipe = this.pipeRegistry.get(proto.name, context);
189-
this._writePipe(proto, pipe);
190-
return pipe;
191208
}
209+
if (isPresent(storedPipe)) {
210+
storedPipe.onDestroy();
211+
}
212+
var pipe = this.pipeRegistry.get(proto.name, context);
213+
this._writePipe(proto, pipe);
214+
return pipe;
192215
}
193216

194217
_readContext(proto:ProtoRecord) {

modules/angular2/src/change_detection/interfaces.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export class ChangeDetector {
5555
addChild(cd:ChangeDetector) {}
5656
removeChild(cd:ChangeDetector) {}
5757
remove() {}
58-
setContext(context:any) {}
58+
hydrate(context:any) {}
59+
dehydrate() {}
5960
markPathToRootAsCheckOnce() {}
6061

6162
detectChanges() {}

modules/angular2/src/change_detection/pipes/pipe.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export var NO_CHANGE = new Object();
22

33
export class Pipe {
44
supports(obj):boolean {return false;}
5+
onDestroy() {}
56
transform(value:any):any {return null;}
67
}

modules/angular2/src/core/compiler/view.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,15 @@ export class View {
9797
// TODO(tbosch): if we have a contextWithLocals we actually only need to
9898
// set the contextWithLocals once. Would it be faster to always use a contextWithLocals
9999
// even if we don't have locals and not update the recordRange here?
100-
this.changeDetector.setContext(this.context);
100+
this.changeDetector.hydrate(this.context);
101101
}
102102

103103
_dehydrateContext() {
104104
if (isPresent(this.contextWithLocals)) {
105105
this.contextWithLocals.clearValues();
106106
}
107107
this.context = null;
108+
this.changeDetector.dehydrate();
108109
}
109110

110111
/**

modules/angular2/test/change_detection/change_detection_spec.js

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {Parser} from 'angular2/src/change_detection/parser/parser';
77
import {Lexer} from 'angular2/src/change_detection/parser/lexer';
88

99
import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWithVariableBindings,
10-
PipeRegistry, NO_CHANGE, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'angular2/change_detection';
10+
PipeRegistry, Pipe, NO_CHANGE, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'angular2/change_detection';
1111

1212
import {ChangeDetectionUtil} from 'angular2/src/change_detection/change_detection_util';
1313

@@ -33,7 +33,7 @@ export function main() {
3333
pcd.addAst(ast(exp), memo, memo);
3434
var dispatcher = new TestDispatcher();
3535
var cd = pcd.instantiate(dispatcher);
36-
cd.setContext(context);
36+
cd.hydrate(context);
3737

3838
return {"changeDetector" : cd, "dispatcher" : dispatcher};
3939
}
@@ -183,7 +183,7 @@ export function main() {
183183

184184
var dispatcher = new TestDispatcher();
185185
var cd = pcd.instantiate(dispatcher);
186-
cd.setContext(new TestData("value"));
186+
cd.hydrate(new TestData("value"));
187187

188188
cd.detectChanges();
189189

@@ -264,7 +264,7 @@ export function main() {
264264
dispatcher.logValue('InvokeC');
265265
return 'c'
266266
};
267-
cd.setContext(tr);
267+
cd.hydrate(tr);
268268

269269
cd.detectChanges();
270270

@@ -280,7 +280,7 @@ export function main() {
280280

281281
var dispatcher = new TestDispatcher();
282282
var cd = pcd.instantiate(dispatcher);
283-
cd.setContext(new TestData('value'));
283+
cd.hydrate(new TestData('value'));
284284

285285
expect(() => {
286286
cd.checkNoChanges();
@@ -295,7 +295,7 @@ export function main() {
295295
pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
296296

297297
var cd = pcd.instantiate(new TestDispatcher());
298-
cd.setContext(null);
298+
cd.hydrate(null);
299299

300300
try {
301301
cd.detectChanges();
@@ -442,6 +442,35 @@ export function main() {
442442
});
443443
});
444444

445+
describe("hydration", () => {
446+
it("should be able to rehydrate a change detector", () => {
447+
var c = createChangeDetector("memo", "name");
448+
var cd = c["changeDetector"];
449+
450+
cd.hydrate("some context");
451+
expect(cd.hydrated()).toBe(true);
452+
453+
cd.dehydrate();
454+
expect(cd.hydrated()).toBe(false);
455+
456+
cd.hydrate("other context");
457+
expect(cd.hydrated()).toBe(true);
458+
});
459+
460+
it("should destroy all active pipes during dehyration", () => {
461+
var pipe = new OncePipe();
462+
var registry = new FakePipeRegistry('pipe', () => pipe);
463+
var c = createChangeDetector("memo", "name | pipe", new Person('bob'), registry);
464+
var cd = c["changeDetector"];
465+
466+
cd.detectChanges();
467+
468+
cd.dehydrate();
469+
470+
expect(pipe.destroyCalled).toBe(true);
471+
});
472+
});
473+
445474
describe("pipes", () => {
446475
it("should support pipes", () => {
447476
var registry = new FakePipeRegistry('pipe', () => new CountingPipe());
@@ -477,6 +506,21 @@ export function main() {
477506

478507
expect(registry.numberOfLookups).toEqual(2);
479508
});
509+
510+
it("should invoke onDestroy on a pipe before switching to another one", () => {
511+
var pipe = new OncePipe();
512+
var registry = new FakePipeRegistry('pipe', () => pipe);
513+
var ctx = new Person("Megatron");
514+
515+
var c = createChangeDetector("memo", "name | pipe", ctx, registry);
516+
var cd = c["changeDetector"];
517+
518+
cd.detectChanges();
519+
ctx.name = "Optimus Prime";
520+
cd.detectChanges();
521+
522+
expect(pipe.destroyCalled).toEqual(true);
523+
});
480524
});
481525

482526
it("should do nothing when returns NO_CHANGE", () => {
@@ -502,10 +546,11 @@ export function main() {
502546
});
503547
}
504548

505-
class CountingPipe {
549+
class CountingPipe extends Pipe {
506550
state:number;
507551

508552
constructor() {
553+
super();
509554
this.state = 0;
510555
}
511556

@@ -518,23 +563,31 @@ class CountingPipe {
518563
}
519564
}
520565

521-
class OncePipe {
566+
class OncePipe extends Pipe {
522567
called:boolean;
568+
destroyCalled:boolean;
569+
523570
constructor() {
571+
super();
524572
this.called = false;;
573+
this.destroyCalled = false;
525574
}
526575

527576
supports(newValue) {
528577
return !this.called;
529578
}
530579

580+
onDestroy() {
581+
this.destroyCalled = true;
582+
}
583+
531584
transform(value) {
532585
this.called = true;
533586
return value;
534587
}
535588
}
536589

537-
class IdentityPipe {
590+
class IdentityPipe extends Pipe {
538591
state:any;
539592

540593
supports(newValue) {

modules/angular2/test/core/compiler/view_spec.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ export function main() {
7676
expect(view.hydrated()).toBe(false);
7777
});
7878

79+
it('should hydrate and dehydrate the change detector', () => {
80+
var ctx = new Object();
81+
view.hydrate(null, null, ctx);
82+
expect(view.changeDetector.hydrated()).toBe(true);
83+
84+
view.dehydrate();
85+
expect(view.changeDetector.hydrated()).toBe(false);
86+
});
87+
7988
it('should use the view pool to reuse views', () => {
8089
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null), null);
8190
var fakeView = new FakeView();

modules/benchmarks/src/change_detection/change_detection_benchmark.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) {
129129
obj.setField(j, i);
130130
}
131131
var cd = proto.instantiate(dispatcher);
132-
cd.setContext(obj);
132+
cd.hydrate(obj);
133133
parentCd.addChild(cd);
134134
}
135135
return parentCd;

0 commit comments

Comments
 (0)