feat(change_detection): added onInit and onCheck hooks
This commit is contained in:
@ -4,13 +4,14 @@ import {AST} from './parser/ast';
|
||||
import {DirectiveIndex, DirectiveRecord} from './directive_record';
|
||||
|
||||
const DIRECTIVE = "directive";
|
||||
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
|
||||
const ELEMENT = "element";
|
||||
const TEXT_NODE = "textNode";
|
||||
|
||||
export class BindingRecord {
|
||||
constructor(public mode: string, public implicitReceiver: any, public ast: AST,
|
||||
public elementIndex: number, public propertyName: string, public setter: SetterFn,
|
||||
public directiveRecord: DirectiveRecord) {}
|
||||
public lifecycleEvent: string, public directiveRecord: DirectiveRecord) {}
|
||||
|
||||
callOnChange() { return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; }
|
||||
|
||||
@ -20,25 +21,42 @@ export class BindingRecord {
|
||||
|
||||
isDirective() { return this.mode === DIRECTIVE; }
|
||||
|
||||
isDirectiveLifecycle() { return this.mode === DIRECTIVE_LIFECYCLE; }
|
||||
|
||||
isElement() { return this.mode === ELEMENT; }
|
||||
|
||||
isTextNode() { return this.mode === TEXT_NODE; }
|
||||
|
||||
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
|
||||
directiveRecord: DirectiveRecord) {
|
||||
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, directiveRecord);
|
||||
return new BindingRecord(DIRECTIVE, 0, ast, 0, propertyName, setter, null, directiveRecord);
|
||||
}
|
||||
|
||||
static createDirectiveOnCheck(directiveRecord: DirectiveRecord) {
|
||||
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, "onCheck",
|
||||
directiveRecord);
|
||||
}
|
||||
|
||||
static createDirectiveOnInit(directiveRecord: DirectiveRecord) {
|
||||
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, "onInit",
|
||||
directiveRecord);
|
||||
}
|
||||
|
||||
static createDirectiveOnChange(directiveRecord: DirectiveRecord) {
|
||||
return new BindingRecord(DIRECTIVE_LIFECYCLE, 0, null, 0, null, null, "onChange",
|
||||
directiveRecord);
|
||||
}
|
||||
|
||||
static createForElement(ast: AST, elementIndex: number, propertyName: string) {
|
||||
return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null);
|
||||
return new BindingRecord(ELEMENT, 0, ast, elementIndex, propertyName, null, null, null);
|
||||
}
|
||||
|
||||
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST, propertyName: string) {
|
||||
return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex,
|
||||
propertyName, null, null);
|
||||
propertyName, null, null, null);
|
||||
}
|
||||
|
||||
static createForTextNode(ast: AST, elementIndex: number) {
|
||||
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null);
|
||||
return new BindingRecord(TEXT_NODE, 0, ast, elementIndex, null, null, null, null);
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ var CHANGES_LOCAL = "changes";
|
||||
var LOCALS_ACCESSOR = "this.locals";
|
||||
var MODE_ACCESSOR = "this.mode";
|
||||
var CURRENT_PROTO = "currentProto";
|
||||
var ALREADY_CHECKED_ACCESSOR = "this.alreadyChecked";
|
||||
|
||||
|
||||
export class ChangeDetectorJITGenerator {
|
||||
@ -86,6 +87,7 @@ export class ChangeDetectorJITGenerator {
|
||||
${PROTOS_ACCESSOR} = protos;
|
||||
${DIRECTIVES_ACCESSOR} = directiveRecords;
|
||||
${LOCALS_ACCESSOR} = null;
|
||||
${ALREADY_CHECKED_ACCESSOR} = false;
|
||||
${this._genFieldDefinitions()}
|
||||
}
|
||||
|
||||
@ -101,6 +103,8 @@ export class ChangeDetectorJITGenerator {
|
||||
context = ${CONTEXT_ACCESSOR};
|
||||
|
||||
${this.records.map((r) => this._genRecord(r)).join("\n")}
|
||||
|
||||
${ALREADY_CHECKED_ACCESSOR} = true;
|
||||
}
|
||||
|
||||
${this.typeName}.prototype.callOnAllChangesDone = function() {
|
||||
@ -113,6 +117,7 @@ export class ChangeDetectorJITGenerator {
|
||||
${LOCALS_ACCESSOR} = locals;
|
||||
${this._genHydrateDirectives()}
|
||||
${this._genHydrateDetectors()}
|
||||
${ALREADY_CHECKED_ACCESSOR} = false;
|
||||
}
|
||||
|
||||
${this.typeName}.prototype.dehydrate = function() {
|
||||
@ -136,7 +141,7 @@ export class ChangeDetectorJITGenerator {
|
||||
}
|
||||
|
||||
_genGetDirectiveFieldNames(): List<string> {
|
||||
return this.directiveRecords.map((d) => this._genGetDirective(d.directiveIndex));
|
||||
return this.directiveRecords.map(d => this._genGetDirective(d.directiveIndex));
|
||||
}
|
||||
|
||||
_genGetDetectorFieldNames(): List<string> {
|
||||
@ -212,10 +217,26 @@ export class ChangeDetectorJITGenerator {
|
||||
}
|
||||
|
||||
_genRecord(r: ProtoRecord): string {
|
||||
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
|
||||
return this._genPipeCheck(r);
|
||||
var rec;
|
||||
if (r.isLifeCycleRecord()) {
|
||||
rec = this._genDirectiveLifecycle(r);
|
||||
} else if (r.isPipeRecord()) {
|
||||
rec = this._genPipeCheck(r);
|
||||
} else {
|
||||
return this._genReferenceCheck(r);
|
||||
rec = this._genReferenceCheck(r);
|
||||
}
|
||||
return `${rec}${this._genLastInDirective(r)}`;
|
||||
}
|
||||
|
||||
_genDirectiveLifecycle(r: ProtoRecord) {
|
||||
if (r.name === "onCheck") {
|
||||
return this._genOnCheck(r);
|
||||
} else if (r.name === "onInit") {
|
||||
return this._genOnInit(r);
|
||||
} else if (r.name === "onChange") {
|
||||
return this._genOnChange(r);
|
||||
} else {
|
||||
throw new BaseException(`Unknown lifecycle event '${r.name}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +269,6 @@ export class ChangeDetectorJITGenerator {
|
||||
${this._genAddToChanges(r)}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
${this._genLastInDirective(r)}
|
||||
`;
|
||||
}
|
||||
|
||||
@ -266,7 +286,6 @@ export class ChangeDetectorJITGenerator {
|
||||
${this._genAddToChanges(r)}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
${this._genLastInDirective(r)}
|
||||
`;
|
||||
|
||||
if (r.isPureFunction()) {
|
||||
@ -390,22 +409,27 @@ export class ChangeDetectorJITGenerator {
|
||||
}
|
||||
|
||||
_genLastInDirective(r: ProtoRecord): string {
|
||||
if (!r.lastInDirective) return "";
|
||||
return `
|
||||
${this._genNotifyOnChanges(r)}
|
||||
${CHANGES_LOCAL} = null;
|
||||
${this._genNotifyOnPushDetectors(r)}
|
||||
${IS_CHANGED_LOCAL} = false;
|
||||
`;
|
||||
}
|
||||
|
||||
_genNotifyOnChanges(r: ProtoRecord): string {
|
||||
_genOnCheck(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
if (!r.lastInDirective || !br.callOnChange()) return "";
|
||||
return `
|
||||
if(${CHANGES_LOCAL}) {
|
||||
${this._genGetDirective(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});
|
||||
${CHANGES_LOCAL} = null;
|
||||
}
|
||||
`;
|
||||
return `if (!throwOnChange) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onCheck();`;
|
||||
}
|
||||
|
||||
_genOnInit(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
return `if (!throwOnChange && !${ALREADY_CHECKED_ACCESSOR}) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onInit();`;
|
||||
}
|
||||
|
||||
_genOnChange(r: ProtoRecord): string {
|
||||
var br = r.bindingRecord;
|
||||
return `if (!throwOnChange && ${CHANGES_LOCAL}) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});`;
|
||||
}
|
||||
|
||||
_genNotifyOnPushDetectors(r: ProtoRecord): string {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {isPresent} from 'angular2/src/facade/lang';
|
||||
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {RECORD_TYPE_SELF, ProtoRecord} from './proto_record';
|
||||
import {RECORD_TYPE_SELF, RECORD_TYPE_DIRECTIVE_LIFECYCLE, ProtoRecord} from './proto_record';
|
||||
|
||||
/**
|
||||
* Removes "duplicate" records. It assuming that record evaluation does not
|
||||
@ -44,7 +44,8 @@ function _selfRecord(r: ProtoRecord, contextIndex: number, selfIndex: number): P
|
||||
}
|
||||
|
||||
function _findMatching(r: ProtoRecord, rs: List<ProtoRecord>) {
|
||||
return ListWrapper.find(rs, (rr) => rr.mode === r.mode && rr.funcOrValue === r.funcOrValue &&
|
||||
return ListWrapper.find(rs, (rr) => rr.mode !== RECORD_TYPE_DIRECTIVE_LIFECYCLE &&
|
||||
rr.mode === r.mode && rr.funcOrValue === r.funcOrValue &&
|
||||
rr.contextIndex === r.contextIndex &&
|
||||
ListWrapper.equals(rr.args, r.args));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {ON_PUSH} from './constants';
|
||||
import {StringWrapper} from 'angular2/src/facade/lang';
|
||||
import {StringWrapper, normalizeBool} from 'angular2/src/facade/lang';
|
||||
|
||||
export class DirectiveIndex {
|
||||
constructor(public elementIndex: number, public directiveIndex: number) {}
|
||||
@ -8,8 +8,29 @@ export class DirectiveIndex {
|
||||
}
|
||||
|
||||
export class DirectiveRecord {
|
||||
constructor(public directiveIndex: DirectiveIndex, public callOnAllChangesDone: boolean,
|
||||
public callOnChange: boolean, public changeDetection: string) {}
|
||||
directiveIndex: DirectiveIndex;
|
||||
callOnAllChangesDone: boolean;
|
||||
callOnChange: boolean;
|
||||
callOnCheck: boolean;
|
||||
callOnInit: boolean;
|
||||
changeDetection: string;
|
||||
|
||||
constructor({directiveIndex, callOnAllChangesDone, callOnChange, callOnCheck, callOnInit,
|
||||
changeDetection}: {
|
||||
directiveIndex?: DirectiveIndex,
|
||||
callOnAllChangesDone?: boolean,
|
||||
callOnChange?: boolean,
|
||||
callOnCheck?: boolean,
|
||||
callOnInit?: boolean,
|
||||
changeDetection?: string
|
||||
} = {}) {
|
||||
this.directiveIndex = directiveIndex;
|
||||
this.callOnAllChangesDone = normalizeBool(callOnAllChangesDone);
|
||||
this.callOnChange = normalizeBool(callOnChange);
|
||||
this.callOnCheck = normalizeBool(callOnCheck);
|
||||
this.callOnInit = normalizeBool(callOnInit);
|
||||
this.changeDetection = changeDetection;
|
||||
}
|
||||
|
||||
isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); }
|
||||
}
|
@ -27,12 +27,13 @@ import {
|
||||
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
|
||||
|
||||
export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
locals: any;
|
||||
locals: any = null;
|
||||
values: List<any>;
|
||||
changes: List<any>;
|
||||
pipes: List<any>;
|
||||
prevContexts: List<any>;
|
||||
directives: any;
|
||||
directives: any = null;
|
||||
alreadyChecked: boolean = false;
|
||||
|
||||
constructor(private changeControlStrategy: string, private dispatcher: any,
|
||||
private pipeRegistry: PipeRegistry, private protos: List<ProtoRecord>,
|
||||
@ -47,8 +48,6 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
ListWrapper.fill(this.pipes, null);
|
||||
ListWrapper.fill(this.prevContexts, uninitialized);
|
||||
ListWrapper.fill(this.changes, false);
|
||||
this.locals = null;
|
||||
this.directives = null;
|
||||
}
|
||||
|
||||
hydrate(context: any, locals: any, directives: any) {
|
||||
@ -56,6 +55,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
this.values[0] = context;
|
||||
this.locals = locals;
|
||||
this.directives = directives;
|
||||
this.alreadyChecked = false;
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
@ -87,19 +87,26 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
var bindingRecord = proto.bindingRecord;
|
||||
var directiveRecord = bindingRecord.directiveRecord;
|
||||
|
||||
var change = this._check(proto, throwOnChange);
|
||||
if (isPresent(change)) {
|
||||
this._updateDirectiveOrElement(change, bindingRecord);
|
||||
isChanged = true;
|
||||
changes = this._addChange(bindingRecord, change, changes);
|
||||
if (proto.isLifeCycleRecord()) {
|
||||
if (proto.name === "onCheck" && !throwOnChange) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onCheck();
|
||||
} else if (proto.name === "onInit" && !throwOnChange && !this.alreadyChecked) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onInit();
|
||||
} else if (proto.name === "onChange" && isPresent(changes) && !throwOnChange) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
|
||||
}
|
||||
|
||||
} else {
|
||||
var change = this._check(proto, throwOnChange);
|
||||
if (isPresent(change)) {
|
||||
this._updateDirectiveOrElement(change, bindingRecord);
|
||||
isChanged = true;
|
||||
changes = this._addChange(bindingRecord, change, changes);
|
||||
}
|
||||
}
|
||||
|
||||
if (proto.lastInDirective) {
|
||||
if (isPresent(changes)) {
|
||||
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
|
||||
changes = null;
|
||||
}
|
||||
|
||||
changes = null;
|
||||
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
|
||||
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
||||
}
|
||||
@ -107,6 +114,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
isChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.alreadyChecked = true;
|
||||
}
|
||||
|
||||
callOnAllChangesDone() {
|
||||
@ -142,7 +151,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||
|
||||
_check(proto: ProtoRecord, throwOnChange: boolean): SimpleChange {
|
||||
try {
|
||||
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
|
||||
if (proto.isPipeRecord()) {
|
||||
return this._pipeCheck(proto, throwOnChange);
|
||||
} else {
|
||||
return this._referenceCheck(proto, throwOnChange);
|
||||
|
@ -53,7 +53,8 @@ import {
|
||||
RECORD_TYPE_BINDING_PIPE,
|
||||
RECORD_TYPE_INTERPOLATE,
|
||||
RECORD_TYPE_SAFE_PROPERTY,
|
||||
RECORD_TYPE_SAFE_INVOKE_METHOD
|
||||
RECORD_TYPE_SAFE_INVOKE_METHOD,
|
||||
RECORD_TYPE_DIRECTIVE_LIFECYCLE
|
||||
} from './proto_record';
|
||||
|
||||
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||
@ -72,7 +73,7 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||
_createRecords(definition: ChangeDetectorDefinition) {
|
||||
var recordBuilder = new ProtoRecordBuilder();
|
||||
ListWrapper.forEach(definition.bindingRecords,
|
||||
(b) => { recordBuilder.addAst(b, definition.variableNames); });
|
||||
(b) => { recordBuilder.add(b, definition.variableNames); });
|
||||
return coalesce(recordBuilder.records);
|
||||
}
|
||||
}
|
||||
@ -91,7 +92,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
|
||||
_createFactory(definition: ChangeDetectorDefinition) {
|
||||
var recordBuilder = new ProtoRecordBuilder();
|
||||
ListWrapper.forEach(definition.bindingRecords,
|
||||
(b) => { recordBuilder.addAst(b, definition.variableNames); });
|
||||
(b) => { recordBuilder.add(b, definition.variableNames); });
|
||||
var c = _jitProtoChangeDetectorClassCounter++;
|
||||
var records = coalesce(recordBuilder.records);
|
||||
var typeName = `ChangeDetector${c}`;
|
||||
@ -106,19 +107,30 @@ class ProtoRecordBuilder {
|
||||
|
||||
constructor() { this.records = []; }
|
||||
|
||||
addAst(b: BindingRecord, variableNames: List<string> = null) {
|
||||
add(b: BindingRecord, variableNames: List<string> = null) {
|
||||
var oldLast = ListWrapper.last(this.records);
|
||||
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
||||
oldLast.lastInDirective = false;
|
||||
}
|
||||
|
||||
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
|
||||
this._appendRecords(b, variableNames);
|
||||
var newLast = ListWrapper.last(this.records);
|
||||
if (isPresent(newLast) && newLast !== oldLast) {
|
||||
newLast.lastInBinding = true;
|
||||
newLast.lastInDirective = true;
|
||||
}
|
||||
}
|
||||
|
||||
_appendRecords(b: BindingRecord, variableNames: List<string>) {
|
||||
if (b.isDirectiveLifecycle()) {
|
||||
;
|
||||
ListWrapper.push(
|
||||
this.records,
|
||||
new ProtoRecord(RECORD_TYPE_DIRECTIVE_LIFECYCLE, b.lifecycleEvent, null, [], [], -1, null,
|
||||
this.records.length + 1, b, null, false, false));
|
||||
} else {
|
||||
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _ConvertAstIntoProtoRecords {
|
||||
|
@ -15,6 +15,7 @@ export const RECORD_TYPE_BINDING_PIPE = 9;
|
||||
export const RECORD_TYPE_INTERPOLATE = 10;
|
||||
export const RECORD_TYPE_SAFE_PROPERTY = 11;
|
||||
export const RECORD_TYPE_SAFE_INVOKE_METHOD = 12;
|
||||
export const RECORD_TYPE_DIRECTIVE_LIFECYCLE = 13;
|
||||
|
||||
export class ProtoRecord {
|
||||
constructor(public mode: number, public name: string, public funcOrValue, public args: List<any>,
|
||||
@ -26,4 +27,10 @@ export class ProtoRecord {
|
||||
isPureFunction(): boolean {
|
||||
return this.mode === RECORD_TYPE_INTERPOLATE || this.mode === RECORD_TYPE_PRIMITIVE_OP;
|
||||
}
|
||||
|
||||
isPipeRecord(): boolean {
|
||||
return this.mode === RECORD_TYPE_PIPE || this.mode === RECORD_TYPE_BINDING_PIPE;
|
||||
}
|
||||
|
||||
isLifeCycleRecord(): boolean { return this.mode === RECORD_TYPE_DIRECTIVE_LIFECYCLE; }
|
||||
}
|
||||
|
Reference in New Issue
Block a user