Skip to content

Commit c39c8eb

Browse files
committed
feat(change_detection): added onInit and onCheck hooks
1 parent 5d2af54 commit c39c8eb

22 files changed

+504
-72
lines changed

modules/angular2/src/change_detection/binding_record.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import {AST} from './parser/ast';
44
import {DirectiveIndex, DirectiveRecord} from './directive_record';
55

66
const DIRECTIVE = "directive";
7+
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
78
const ELEMENT = "element";
89
const TEXT_NODE = "textNode";
910

1011
export class BindingRecord {
1112
constructor(public mode: string, public implicitReceiver: any, public ast: AST,
1213
public elementIndex: number, public propertyName: string, public setter: SetterFn,
13-
public directiveRecord: DirectiveRecord) {}
14+
public lifecycleEvent: string, public directiveRecord: DirectiveRecord) {}
1415

1516
callOnChange() { return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; }
1617

@@ -20,25 +21,42 @@ export class BindingRecord {
2021

2122
isDirective() { return this.mode === DIRECTIVE; }
2223

24+
isDirectiveLifecycle() { return this.mode === DIRECTIVE_LIFECYCLE; }
25+
2326
isElement() { return this.mode === ELEMENT; }
2427

2528
isTextNode() { return this.mode === TEXT_NODE; }
2629

2730
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
2831
directiveRecord: DirectiveRecord) {
29-
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, directiveRecord);
32+
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, null, directiveRecord);
33+
}
34+
35+
static createDirectiveOnCheck(directiveRecord: DirectiveRecord) {
36+
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, "onCheck",
37+
directiveRecord);
38+
}
39+
40+
static createDirectiveOnInit(directiveRecord: DirectiveRecord) {
41+
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, "onInit",
42+
directiveRecord);
43+
}
44+
45+
static createDirectiveOnChange(directiveRecord: DirectiveRecord) {
46+
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, "onChange",
47+
directiveRecord);
3048
}
3149

3250
static createForElement(ast: AST, elementIndex: number, propertyName: string) {
33-
return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null);
51+
return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null, null);
3452
}
3553

3654
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST, propertyName: string) {
3755
return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex,
38-
propertyName, null, null);
56+
propertyName, null, null, null);
3957
}
4058

4159
static createForTextNode(ast: AST, elementIndex: number) {
42-
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null);
60+
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null, null);
4361
}
4462
}

modules/angular2/src/change_detection/change_detection_jit_generator.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var CHANGES_LOCAL = "changes";
4040
var LOCALS_ACCESSOR = "this.locals";
4141
var MODE_ACCESSOR = "this.mode";
4242
var CURRENT_PROTO = "currentProto";
43+
var ALREADY_CHECKED_ACCESSOR = "this.alreadyChecked";
4344

4445

4546
export class ChangeDetectorJITGenerator {
@@ -86,6 +87,7 @@ export class ChangeDetectorJITGenerator {
8687
${PROTOS_ACCESSOR} = protos;
8788
${DIRECTIVES_ACCESSOR} = directiveRecords;
8889
${LOCALS_ACCESSOR} = null;
90+
${ALREADY_CHECKED_ACCESSOR} = false;
8991
${this._genFieldDefinitions()}
9092
}
9193
@@ -101,6 +103,8 @@ export class ChangeDetectorJITGenerator {
101103
context = ${CONTEXT_ACCESSOR};
102104
103105
${this.records.map((r) => this._genRecord(r)).join("\n")}
106+
107+
${ALREADY_CHECKED_ACCESSOR} = true;
104108
}
105109
106110
${this.typeName}.prototype.callOnAllChangesDone = function() {
@@ -113,6 +117,7 @@ export class ChangeDetectorJITGenerator {
113117
${LOCALS_ACCESSOR} = locals;
114118
${this._genHydrateDirectives()}
115119
${this._genHydrateDetectors()}
120+
${ALREADY_CHECKED_ACCESSOR} = false;
116121
}
117122
118123
${this.typeName}.prototype.dehydrate = function() {
@@ -136,7 +141,7 @@ export class ChangeDetectorJITGenerator {
136141
}
137142

138143
_genGetDirectiveFieldNames(): List<string> {
139-
return this.directiveRecords.map((d) => this._genGetDirective(d.directiveIndex));
144+
return this.directiveRecords.map(d => this._genGetDirective(d.directiveIndex));
140145
}
141146

142147
_genGetDetectorFieldNames(): List<string> {
@@ -212,10 +217,26 @@ export class ChangeDetectorJITGenerator {
212217
}
213218

214219
_genRecord(r: ProtoRecord): string {
215-
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
216-
return this._genPipeCheck(r);
220+
var rec;
221+
if (r.isLifeCycleRecord()) {
222+
rec = this._genDirectiveLifecycle(r);
223+
} else if (r.isPipeRecord()) {
224+
rec = this._genPipeCheck(r);
217225
} else {
218-
return this._genReferenceCheck(r);
226+
rec = this._genReferenceCheck(r);
227+
}
228+
return `${rec}${this._genLastInDirective(r)}`;
229+
}
230+
231+
_genDirectiveLifecycle(r: ProtoRecord) {
232+
if (r.name === "onCheck") {
233+
return this._genOnCheck(r);
234+
} else if (r.name === "onInit") {
235+
return this._genOnInit(r);
236+
} else if (r.name === "onChange") {
237+
return this._genOnChange(r);
238+
} else {
239+
throw new BaseException(`Unknown lifecycle event '${r.name}'`);
219240
}
220241
}
221242

@@ -248,7 +269,6 @@ export class ChangeDetectorJITGenerator {
248269
${this._genAddToChanges(r)}
249270
${oldValue} = ${newValue};
250271
}
251-
${this._genLastInDirective(r)}
252272
`;
253273
}
254274

@@ -266,7 +286,6 @@ export class ChangeDetectorJITGenerator {
266286
${this._genAddToChanges(r)}
267287
${oldValue} = ${newValue};
268288
}
269-
${this._genLastInDirective(r)}
270289
`;
271290

272291
if (r.isPureFunction()) {
@@ -390,22 +409,27 @@ export class ChangeDetectorJITGenerator {
390409
}
391410

392411
_genLastInDirective(r: ProtoRecord): string {
412+
if (!r.lastInDirective) return "";
393413
return `
394-
${this._genNotifyOnChanges(r)}
414+
${CHANGES_LOCAL} = null;
395415
${this._genNotifyOnPushDetectors(r)}
396416
${IS_CHANGED_LOCAL} = false;
397417
`;
398418
}
399419

400-
_genNotifyOnChanges(r: ProtoRecord): string {
420+
_genOnCheck(r: ProtoRecord): string {
401421
var br = r.bindingRecord;
402-
if (!r.lastInDirective || !br.callOnChange()) return "";
403-
return `
404-
if(${CHANGES_LOCAL}) {
405-
${this._genGetDirective(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});
406-
${CHANGES_LOCAL} = null;
407-
}
408-
`;
422+
return `if (!throwOnChange) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onCheck();`;
423+
}
424+
425+
_genOnInit(r: ProtoRecord): string {
426+
var br = r.bindingRecord;
427+
return `if (!throwOnChange && !${ALREADY_CHECKED_ACCESSOR}) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onInit();`;
428+
}
429+
430+
_genOnChange(r: ProtoRecord): string {
431+
var br = r.bindingRecord;
432+
return `if (!throwOnChange && ${CHANGES_LOCAL}) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});`;
409433
}
410434

411435
_genNotifyOnPushDetectors(r: ProtoRecord): string {

modules/angular2/src/change_detection/coalesce.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {isPresent} from 'angular2/src/facade/lang';
22
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
3-
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_record';
3+
import {RECORD_TYPE_SELF, RECORD_TYPE_DIRECTIVE_LIFECYCLE, ProtoRecord} from './proto_record';
44

55
/**
66
* Removes "duplicate" records. It assuming that record evaluation does not
@@ -44,7 +44,8 @@ function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): P
4444
}
4545

4646
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
47-
return ListWrapper.find(rs, (rr) => rr.mode === r.mode && rr.funcOrValue === r.funcOrValue &&
47+
return ListWrapper.find(rs, (rr) => rr.mode !== RECORD_TYPE_DIRECTIVE_LIFECYCLE &&
48+
rr.mode === r.mode && rr.funcOrValue === r.funcOrValue &&
4849
rr.contextIndex === r.contextIndex &&
4950
ListWrapper.equals(rr.args, r.args));
5051
}
Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {ON_PUSH} from './constants';
2-
import {StringWrapper} from 'angular2/src/facade/lang';
2+
import {StringWrapper, normalizeBool} from 'angular2/src/facade/lang';
33

44
export class DirectiveIndex {
55
constructor(public elementIndex: number, public directiveIndex: number) {}
@@ -8,8 +8,29 @@ export class DirectiveIndex {
88
}
99

1010
export class DirectiveRecord {
11-
constructor(public directiveIndex: DirectiveIndex, public callOnAllChangesDone: boolean,
12-
public callOnChange: boolean, public changeDetection: string) {}
11+
directiveIndex: DirectiveIndex;
12+
callOnAllChangesDone: boolean;
13+
callOnChange: boolean;
14+
callOnCheck: boolean;
15+
callOnInit: boolean;
16+
changeDetection: string;
17+
18+
constructor({directiveIndex, callOnAllChangesDone, callOnChange, callOnCheck, callOnInit,
19+
changeDetection}: {
20+
directiveIndex?: DirectiveIndex,
21+
callOnAllChangesDone?: boolean,
22+
callOnChange?: boolean,
23+
callOnCheck?: boolean,
24+
callOnInit?: boolean,
25+
changeDetection?: string
26+
} = {}) {
27+
this.directiveIndex = directiveIndex;
28+
this.callOnAllChangesDone = normalizeBool(callOnAllChangesDone);
29+
this.callOnChange = normalizeBool(callOnChange);
30+
this.callOnCheck = normalizeBool(callOnCheck);
31+
this.callOnInit = normalizeBool(callOnInit);
32+
this.changeDetection = changeDetection;
33+
}
1334

1435
isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); }
1536
}

modules/angular2/src/change_detection/dynamic_change_detector.ts

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ import {
2727
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
2828

2929
export class DynamicChangeDetector extends AbstractChangeDetector {
30-
locals: any;
30+
locals: any = null;
3131
values: List<any>;
3232
changes: List<any>;
3333
pipes: List<any>;
3434
prevContexts: List<any>;
35-
directives: any;
35+
directives: any = null;
36+
alreadyChecked: boolean = false;
3637

3738
constructor(private changeControlStrategy: string, private dispatcher: any,
3839
private pipeRegistry: PipeRegistry, private protos: List<ProtoRecord>,
@@ -47,15 +48,14 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
4748
ListWrapper.fill(this.pipes, null);
4849
ListWrapper.fill(this.prevContexts, uninitialized);
4950
ListWrapper.fill(this.changes, false);
50-
this.locals = null;
51-
this.directives = null;
5251
}
5352

5453
hydrate(context: any, locals: any, directives: any) {
5554
this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy);
5655
this.values[0] = context;
5756
this.locals = locals;
5857
this.directives = directives;
58+
this.alreadyChecked = false;
5959
}
6060

6161
dehydrate() {
@@ -87,26 +87,35 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
8787
var bindingRecord = proto.bindingRecord;
8888
var directiveRecord = bindingRecord.directiveRecord;
8989

90-
var change = this._check(proto, throwOnChange);
91-
if (isPresent(change)) {
92-
this._updateDirectiveOrElement(change, bindingRecord);
93-
isChanged = true;
94-
changes = this._addChange(bindingRecord, change, changes);
95-
}
96-
97-
if (proto.lastInDirective) {
98-
if (isPresent(changes)) {
90+
if (proto.isLifeCycleRecord()) {
91+
if (proto.name === "onCheck" && !throwOnChange) {
92+
this._getDirectiveFor(directiveRecord.directiveIndex).onCheck();
93+
} else if (proto.name === "onInit" && !throwOnChange && !this.alreadyChecked) {
94+
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
95+
} else if (proto.name === "onChange" && isPresent(changes) && !throwOnChange) {
9996
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
100-
changes = null;
10197
}
10298

99+
} else {
100+
var change = this._check(proto, throwOnChange);
101+
if (isPresent(change)) {
102+
this._updateDirectiveOrElement(change, bindingRecord);
103+
isChanged = true;
104+
changes = this._addChange(bindingRecord, change, changes);
105+
}
106+
}
107+
108+
if (proto.lastInDirective) {
109+
changes = null;
103110
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
104111
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
105112
}
106113

107114
isChanged = false;
108115
}
109116
}
117+
118+
this.alreadyChecked = true;
110119
}
111120

112121
callOnAllChangesDone() {
@@ -142,7 +151,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
142151

143152
_check(proto: ProtoRecord, throwOnChange: boolean): SimpleChange {
144153
try {
145-
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
154+
if (proto.isPipeRecord()) {
146155
return this._pipeCheck(proto, throwOnChange);
147156
} else {
148157
return this._referenceCheck(proto, throwOnChange);

modules/angular2/src/change_detection/proto_change_detector.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ import {
5353
RECORD_TYPE_BINDING_PIPE,
5454
RECORD_TYPE_INTERPOLATE,
5555
RECORD_TYPE_SAFE_PROPERTY,
56-
RECORD_TYPE_SAFE_INVOKE_METHOD
56+
RECORD_TYPE_SAFE_INVOKE_METHOD,
57+
RECORD_TYPE_DIRECTIVE_LIFECYCLE
5758
} from './proto_record';
5859

5960
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
@@ -72,7 +73,7 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
7273
_createRecords(definition: ChangeDetectorDefinition) {
7374
var recordBuilder = new ProtoRecordBuilder();
7475
ListWrapper.forEach(definition.bindingRecords,
75-
(b) => { recordBuilder.addAst(b, definition.variableNames); });
76+
(b) => { recordBuilder.add(b, definition.variableNames); });
7677
return coalesce(recordBuilder.records);
7778
}
7879
}
@@ -91,7 +92,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
9192
_createFactory(definition: ChangeDetectorDefinition) {
9293
var recordBuilder = new ProtoRecordBuilder();
9394
ListWrapper.forEach(definition.bindingRecords,
94-
(b) => { recordBuilder.addAst(b, definition.variableNames); });
95+
(b) => { recordBuilder.add(b, definition.variableNames); });
9596
var c = _jitProtoChangeDetectorClassCounter++;
9697
var records = coalesce(recordBuilder.records);
9798
var typeName = `ChangeDetector${c}`;
@@ -106,19 +107,30 @@ class ProtoRecordBuilder {
106107

107108
constructor() { this.records = []; }
108109

109-
addAst(b: BindingRecord, variableNames: List<string> = null) {
110+
add(b: BindingRecord, variableNames: List<string> = null) {
110111
var oldLast = ListWrapper.last(this.records);
111112
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
112113
oldLast.lastInDirective = false;
113114
}
114-
115-
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
115+
this._appendRecords(b, variableNames);
116116
var newLast = ListWrapper.last(this.records);
117117
if (isPresent(newLast) && newLast !== oldLast) {
118118
newLast.lastInBinding = true;
119119
newLast.lastInDirective = true;
120120
}
121121
}
122+
123+
_appendRecords(b: BindingRecord, variableNames: List<string>) {
124+
if (b.isDirectiveLifecycle()) {
125+
;
126+
ListWrapper.push(
127+
this.records,
128+
new ProtoRecord(RECORD_TYPE_DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [], [], -1, null,
129+
this.records.length + 1, b, null, false, false));
130+
} else {
131+
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
132+
}
133+
}
122134
}
123135

124136
class _ConvertAstIntoProtoRecords {

0 commit comments

Comments
 (0)