Skip to content

Commit 23a0800

Browse files
vsavkinalexeagle
authored andcommitted
feat(change_detection): add mode to ChangeDetector
1 parent 3067601 commit 23a0800

File tree

4 files changed

+200
-1
lines changed

4 files changed

+200
-1
lines changed

modules/change_detection/src/abstract_change_detector.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
import {isPresent} from 'facade/src/lang';
12
import {List, ListWrapper} from 'facade/src/collection';
2-
import {ChangeDetector} from './interfaces';
3+
import {ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
34

45
export class AbstractChangeDetector extends ChangeDetector {
56
children:List;
67
parent:ChangeDetector;
8+
status:string;
79

810
constructor() {
911
this.children = [];
12+
this.status = CHECK_ALWAYS;
1013
}
1114

1215
addChild(cd:ChangeDetector) {
@@ -31,8 +34,20 @@ export class AbstractChangeDetector extends ChangeDetector {
3134
}
3235

3336
_detectChanges(throwOnChange:boolean) {
37+
if (this.mode === DETACHED || this.mode === CHECKED) return;
38+
3439
this.detectChangesInRecords(throwOnChange);
3540
this._detectChangesInChildren(throwOnChange);
41+
42+
if (this.mode === CHECK_ONCE) this.mode = CHECKED;
43+
}
44+
45+
markAsCheckOnce(){
46+
var c = this;
47+
while(isPresent(c) && (c.mode === CHECKED || c.mode === CHECK_ALWAYS)) {
48+
if (c.mode === CHECKED) c.mode = CHECK_ONCE;
49+
c = c.parent;
50+
}
3651
}
3752

3853
detectChangesInRecords(throwOnChange:boolean){}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {isPresent} from 'facade/lang';
2+
import {List, ListWrapper, Map, MapWrapper} from 'facade/collection';
3+
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_change_detector';
4+
5+
/**
6+
* Removes "duplicate" records. It assuming that record evaluation does not
7+
* have side-effects.
8+
*
9+
* Records that are not last in bindings are removed and all the indices
10+
* of the records that depend on them are updated.
11+
*
12+
* Records that are last in bindings CANNOT be removed, and instead are
13+
* replaced with very cheap SELF records.
14+
*/
15+
export function coalesce(records:List<ProtoRecord>):List<ProtoRecord> {
16+
var res = ListWrapper.create();
17+
var indexMap = MapWrapper.create();
18+
19+
for (var i = 0; i < records.length; ++i) {
20+
var r = records[i];
21+
var record = _replaceIndices(r, res.length + 1, indexMap);
22+
var matchingRecord = _findMatching(record, res);
23+
24+
if (isPresent(matchingRecord) && record.lastInBinding) {
25+
ListWrapper.push(res, _selfRecord(record, matchingRecord.selfIndex, res.length + 1));
26+
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
27+
28+
} else if (isPresent(matchingRecord) && !record.lastInBinding) {
29+
MapWrapper.set(indexMap, r.selfIndex, matchingRecord.selfIndex);
30+
31+
} else {
32+
ListWrapper.push(res, record);
33+
MapWrapper.set(indexMap, r.selfIndex, record.selfIndex);
34+
}
35+
}
36+
37+
return res;
38+
}
39+
40+
function _selfRecord(r:ProtoRecord, contextIndex:number, selfIndex:number):ProtoRecord {
41+
return new ProtoRecord(
42+
RECORD_TYPE_SELF,
43+
"self",
44+
null,
45+
[],
46+
r.fixedArgs,
47+
contextIndex,
48+
selfIndex,
49+
r.bindingMemento,
50+
r.groupMemento,
51+
r.expressionAsString,
52+
r.lastInBinding,
53+
r.lastInGroup
54+
);
55+
}
56+
57+
function _findMatching(r:ProtoRecord, rs:List<ProtoRecord>){
58+
return ListWrapper.find(rs, (rr) =>
59+
rr.mode === r.mode &&
60+
rr.funcOrValue === r.funcOrValue &&
61+
rr.contextIndex === r.contextIndex &&
62+
ListWrapper.equals(rr.args, r.args)
63+
);
64+
}
65+
66+
function _replaceIndices(r:ProtoRecord, selfIndex:number, indexMap:Map) {
67+
var args = ListWrapper.map(r.args, (a) => _map(indexMap, a));
68+
var contextIndex = _map(indexMap, r.contextIndex);
69+
return new ProtoRecord(
70+
r.mode,
71+
r.name,
72+
r.funcOrValue,
73+
args,
74+
r.fixedArgs,
75+
contextIndex,
76+
selfIndex,
77+
r.bindingMemento,
78+
r.groupMemento,
79+
r.expressionAsString,
80+
r.lastInBinding,
81+
r.lastInGroup
82+
);
83+
}
84+
85+
function _map(indexMap:Map, value:number) {
86+
var r = MapWrapper.get(indexMap, value)
87+
return isPresent(r) ? r : value;
88+
}

modules/change_detection/src/interfaces.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,38 @@ export class ChangeRecord {
1919
}
2020
}
2121

22+
23+
/**
24+
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
25+
* will become CHECKED.
26+
*/
27+
export const CHECK_ONCE="CHECK_ONCE";
28+
29+
/**
30+
* CHECKED means that the change detector should be skipped until its mode changes to
31+
* CHECK_ONCE or CHECK_ALWAYS.
32+
*/
33+
export const CHECKED="CHECKED";
34+
35+
/**
36+
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
37+
* will remain CHECK_ALWAYS.
38+
*/
39+
export const CHECK_ALWAYS="ALWAYS_CHECK";
40+
41+
/**
42+
* DETACHED means that the change detector sub tree is not a part of the main tree and
43+
* should be skipped.
44+
*/
45+
export const DETACHED="DETACHED";
46+
2247
export class ChangeDispatcher {
2348
onRecordChange(groupMemento, records:List<ChangeRecord>) {}
2449
}
2550

2651
export class ChangeDetector {
2752
parent:ChangeDetector;
53+
mode:string;
2854

2955
addChild(cd:ChangeDetector) {}
3056
removeChild(cd:ChangeDetector) {}

modules/change_detection/test/change_detection_spec.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWi
1313

1414

1515
import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'change_detection/src/proto_change_detector';
16+
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'change_detection/src/interfaces';
1617

1718

1819
export function main() {
@@ -473,6 +474,75 @@ export function main() {
473474
expect(count).toEqual(1);
474475
});
475476
});
477+
478+
describe("mode", () => {
479+
it("should not check a detached change detector", () => {
480+
var c = createChangeDetector('name', 'a', new TestData("value"));
481+
var cd = c["changeDetector"];
482+
var dispatcher = c["dispatcher"];
483+
484+
cd.mode = DETACHED;
485+
cd.detectChanges();
486+
487+
expect(dispatcher.log).toEqual([]);
488+
});
489+
490+
it("should not check a checked change detector", () => {
491+
var c = createChangeDetector('name', 'a', new TestData("value"));
492+
var cd = c["changeDetector"];
493+
var dispatcher = c["dispatcher"];
494+
495+
cd.mode = CHECKED;
496+
cd.detectChanges();
497+
498+
expect(dispatcher.log).toEqual([]);
499+
});
500+
501+
it("should change CHECK_ONCE to CHECKED", () => {
502+
var cd = createProtoChangeDetector().instantiate(null, null);
503+
cd.mode = CHECK_ONCE;
504+
505+
cd.detectChanges();
506+
507+
expect(cd.mode).toEqual(CHECKED);
508+
});
509+
510+
it("should not change the CHECK_ALWAYS", () => {
511+
var cd = createProtoChangeDetector().instantiate(null, null);
512+
cd.mode = CHECK_ALWAYS;
513+
514+
cd.detectChanges();
515+
516+
expect(cd.mode).toEqual(CHECK_ALWAYS);
517+
});
518+
});
519+
520+
describe("markAsCheckOnce", () => {
521+
function changeDetector(mode, parent) {
522+
var cd = createProtoChangeDetector().instantiate(null, null);
523+
cd.mode = mode;
524+
if (isPresent(parent)) parent.addChild(cd);
525+
return cd;
526+
}
527+
528+
it("should mark all checked detectors as CHECK_ONCE " +
529+
"until reaching a detached one", () => {
530+
531+
var root = changeDetector(CHECK_ALWAYS, null);
532+
var disabled = changeDetector(DETACHED, root);
533+
var parent = changeDetector(CHECKED, disabled);
534+
var child = changeDetector(CHECK_ALWAYS, parent);
535+
var grandChild = changeDetector(CHECKED, child);
536+
537+
grandChild.markAsCheckOnce();
538+
539+
expect(root.mode).toEqual(CHECK_ALWAYS);
540+
expect(disabled.mode).toEqual(DETACHED);
541+
expect(parent.mode).toEqual(CHECK_ONCE);
542+
expect(child.mode).toEqual(CHECK_ALWAYS);
543+
expect(grandChild.mode).toEqual(CHECK_ONCE);
544+
});
545+
});
476546
});
477547
});
478548
}

0 commit comments

Comments
 (0)