feat(change_detection): added onInit and onCheck hooks
This commit is contained in:
parent
5d2af54730
commit
c39c8ebcd0
|
@ -4,13 +4,14 @@ import {AST} from './parser/ast';
|
||||||
import {DirectiveIndex, DirectiveRecord} from './directive_record';
|
import {DirectiveIndex, DirectiveRecord} from './directive_record';
|
||||||
|
|
||||||
const DIRECTIVE = "directive";
|
const DIRECTIVE = "directive";
|
||||||
|
const DIRECTIVE_LIFECYCLE = "directiveLifecycle";
|
||||||
const ELEMENT = "element";
|
const ELEMENT = "element";
|
||||||
const TEXT_NODE = "textNode";
|
const TEXT_NODE = "textNode";
|
||||||
|
|
||||||
export class BindingRecord {
|
export class BindingRecord {
|
||||||
constructor(public mode: string, public implicitReceiver: any, public ast: AST,
|
constructor(public mode: string, public implicitReceiver: any, public ast: AST,
|
||||||
public elementIndex: number, public propertyName: string, public setter: SetterFn,
|
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; }
|
callOnChange() { return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; }
|
||||||
|
|
||||||
|
@ -20,25 +21,42 @@ export class BindingRecord {
|
||||||
|
|
||||||
isDirective() { return this.mode === DIRECTIVE; }
|
isDirective() { return this.mode === DIRECTIVE; }
|
||||||
|
|
||||||
|
isDirectiveLifecycle() { return this.mode === DIRECTIVE_LIFECYCLE; }
|
||||||
|
|
||||||
isElement() { return this.mode === ELEMENT; }
|
isElement() { return this.mode === ELEMENT; }
|
||||||
|
|
||||||
isTextNode() { return this.mode === TEXT_NODE; }
|
isTextNode() { return this.mode === TEXT_NODE; }
|
||||||
|
|
||||||
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
|
static createForDirective(ast: AST, propertyName: string, setter: SetterFn,
|
||||||
directiveRecord: DirectiveRecord) {
|
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) {
|
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) {
|
static createForHostProperty(directiveIndex: DirectiveIndex, ast: AST, propertyName: string) {
|
||||||
return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex,
|
return new BindingRecord(ELEMENT, directiveIndex, ast, directiveIndex.elementIndex,
|
||||||
propertyName, null, null);
|
propertyName, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
static createForTextNode(ast: AST, elementIndex: number) {
|
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 LOCALS_ACCESSOR = "this.locals";
|
||||||
var MODE_ACCESSOR = "this.mode";
|
var MODE_ACCESSOR = "this.mode";
|
||||||
var CURRENT_PROTO = "currentProto";
|
var CURRENT_PROTO = "currentProto";
|
||||||
|
var ALREADY_CHECKED_ACCESSOR = "this.alreadyChecked";
|
||||||
|
|
||||||
|
|
||||||
export class ChangeDetectorJITGenerator {
|
export class ChangeDetectorJITGenerator {
|
||||||
|
@ -86,6 +87,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
${PROTOS_ACCESSOR} = protos;
|
${PROTOS_ACCESSOR} = protos;
|
||||||
${DIRECTIVES_ACCESSOR} = directiveRecords;
|
${DIRECTIVES_ACCESSOR} = directiveRecords;
|
||||||
${LOCALS_ACCESSOR} = null;
|
${LOCALS_ACCESSOR} = null;
|
||||||
|
${ALREADY_CHECKED_ACCESSOR} = false;
|
||||||
${this._genFieldDefinitions()}
|
${this._genFieldDefinitions()}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +103,8 @@ export class ChangeDetectorJITGenerator {
|
||||||
context = ${CONTEXT_ACCESSOR};
|
context = ${CONTEXT_ACCESSOR};
|
||||||
|
|
||||||
${this.records.map((r) => this._genRecord(r)).join("\n")}
|
${this.records.map((r) => this._genRecord(r)).join("\n")}
|
||||||
|
|
||||||
|
${ALREADY_CHECKED_ACCESSOR} = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
${this.typeName}.prototype.callOnAllChangesDone = function() {
|
${this.typeName}.prototype.callOnAllChangesDone = function() {
|
||||||
|
@ -113,6 +117,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
${LOCALS_ACCESSOR} = locals;
|
${LOCALS_ACCESSOR} = locals;
|
||||||
${this._genHydrateDirectives()}
|
${this._genHydrateDirectives()}
|
||||||
${this._genHydrateDetectors()}
|
${this._genHydrateDetectors()}
|
||||||
|
${ALREADY_CHECKED_ACCESSOR} = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
${this.typeName}.prototype.dehydrate = function() {
|
${this.typeName}.prototype.dehydrate = function() {
|
||||||
|
@ -136,7 +141,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
_genGetDirectiveFieldNames(): List<string> {
|
_genGetDirectiveFieldNames(): List<string> {
|
||||||
return this.directiveRecords.map((d) => this._genGetDirective(d.directiveIndex));
|
return this.directiveRecords.map(d => this._genGetDirective(d.directiveIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
_genGetDetectorFieldNames(): List<string> {
|
_genGetDetectorFieldNames(): List<string> {
|
||||||
|
@ -212,10 +217,26 @@ export class ChangeDetectorJITGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
_genRecord(r: ProtoRecord): string {
|
_genRecord(r: ProtoRecord): string {
|
||||||
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
|
var rec;
|
||||||
return this._genPipeCheck(r);
|
if (r.isLifeCycleRecord()) {
|
||||||
|
rec = this._genDirectiveLifecycle(r);
|
||||||
|
} else if (r.isPipeRecord()) {
|
||||||
|
rec = this._genPipeCheck(r);
|
||||||
} else {
|
} 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)}
|
${this._genAddToChanges(r)}
|
||||||
${oldValue} = ${newValue};
|
${oldValue} = ${newValue};
|
||||||
}
|
}
|
||||||
${this._genLastInDirective(r)}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +286,6 @@ export class ChangeDetectorJITGenerator {
|
||||||
${this._genAddToChanges(r)}
|
${this._genAddToChanges(r)}
|
||||||
${oldValue} = ${newValue};
|
${oldValue} = ${newValue};
|
||||||
}
|
}
|
||||||
${this._genLastInDirective(r)}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (r.isPureFunction()) {
|
if (r.isPureFunction()) {
|
||||||
|
@ -390,22 +409,27 @@ export class ChangeDetectorJITGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
_genLastInDirective(r: ProtoRecord): string {
|
_genLastInDirective(r: ProtoRecord): string {
|
||||||
|
if (!r.lastInDirective) return "";
|
||||||
return `
|
return `
|
||||||
${this._genNotifyOnChanges(r)}
|
${CHANGES_LOCAL} = null;
|
||||||
${this._genNotifyOnPushDetectors(r)}
|
${this._genNotifyOnPushDetectors(r)}
|
||||||
${IS_CHANGED_LOCAL} = false;
|
${IS_CHANGED_LOCAL} = false;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
_genNotifyOnChanges(r: ProtoRecord): string {
|
_genOnCheck(r: ProtoRecord): string {
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
if (!r.lastInDirective || !br.callOnChange()) return "";
|
return `if (!throwOnChange) ${this._genGetDirective(br.directiveRecord.directiveIndex)}.onCheck();`;
|
||||||
return `
|
|
||||||
if(${CHANGES_LOCAL}) {
|
|
||||||
${this._genGetDirective(br.directiveRecord.directiveIndex)}.onChange(${CHANGES_LOCAL});
|
|
||||||
${CHANGES_LOCAL} = null;
|
|
||||||
}
|
}
|
||||||
`;
|
|
||||||
|
_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 {
|
_genNotifyOnPushDetectors(r: ProtoRecord): string {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {isPresent} from 'angular2/src/facade/lang';
|
import {isPresent} from 'angular2/src/facade/lang';
|
||||||
import {List, ListWrapper, Map, MapWrapper} from 'angular2/src/facade/collection';
|
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
|
* 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>) {
|
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 &&
|
rr.contextIndex === r.contextIndex &&
|
||||||
ListWrapper.equals(rr.args, r.args));
|
ListWrapper.equals(rr.args, r.args));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {ON_PUSH} from './constants';
|
import {ON_PUSH} from './constants';
|
||||||
import {StringWrapper} from 'angular2/src/facade/lang';
|
import {StringWrapper, normalizeBool} from 'angular2/src/facade/lang';
|
||||||
|
|
||||||
export class DirectiveIndex {
|
export class DirectiveIndex {
|
||||||
constructor(public elementIndex: number, public directiveIndex: number) {}
|
constructor(public elementIndex: number, public directiveIndex: number) {}
|
||||||
|
@ -8,8 +8,29 @@ export class DirectiveIndex {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DirectiveRecord {
|
export class DirectiveRecord {
|
||||||
constructor(public directiveIndex: DirectiveIndex, public callOnAllChangesDone: boolean,
|
directiveIndex: DirectiveIndex;
|
||||||
public callOnChange: boolean, public changeDetection: string) {}
|
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); }
|
isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); }
|
||||||
}
|
}
|
|
@ -27,12 +27,13 @@ import {
|
||||||
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
|
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
|
||||||
|
|
||||||
export class DynamicChangeDetector extends AbstractChangeDetector {
|
export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
locals: any;
|
locals: any = null;
|
||||||
values: List<any>;
|
values: List<any>;
|
||||||
changes: List<any>;
|
changes: List<any>;
|
||||||
pipes: List<any>;
|
pipes: List<any>;
|
||||||
prevContexts: List<any>;
|
prevContexts: List<any>;
|
||||||
directives: any;
|
directives: any = null;
|
||||||
|
alreadyChecked: boolean = false;
|
||||||
|
|
||||||
constructor(private changeControlStrategy: string, private dispatcher: any,
|
constructor(private changeControlStrategy: string, private dispatcher: any,
|
||||||
private pipeRegistry: PipeRegistry, private protos: List<ProtoRecord>,
|
private pipeRegistry: PipeRegistry, private protos: List<ProtoRecord>,
|
||||||
|
@ -47,8 +48,6 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
ListWrapper.fill(this.pipes, null);
|
ListWrapper.fill(this.pipes, null);
|
||||||
ListWrapper.fill(this.prevContexts, uninitialized);
|
ListWrapper.fill(this.prevContexts, uninitialized);
|
||||||
ListWrapper.fill(this.changes, false);
|
ListWrapper.fill(this.changes, false);
|
||||||
this.locals = null;
|
|
||||||
this.directives = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hydrate(context: any, locals: any, directives: any) {
|
hydrate(context: any, locals: any, directives: any) {
|
||||||
|
@ -56,6 +55,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
this.values[0] = context;
|
this.values[0] = context;
|
||||||
this.locals = locals;
|
this.locals = locals;
|
||||||
this.directives = directives;
|
this.directives = directives;
|
||||||
|
this.alreadyChecked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
dehydrate() {
|
dehydrate() {
|
||||||
|
@ -87,19 +87,26 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
var bindingRecord = proto.bindingRecord;
|
var bindingRecord = proto.bindingRecord;
|
||||||
var directiveRecord = bindingRecord.directiveRecord;
|
var directiveRecord = bindingRecord.directiveRecord;
|
||||||
|
|
||||||
|
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);
|
var change = this._check(proto, throwOnChange);
|
||||||
if (isPresent(change)) {
|
if (isPresent(change)) {
|
||||||
this._updateDirectiveOrElement(change, bindingRecord);
|
this._updateDirectiveOrElement(change, bindingRecord);
|
||||||
isChanged = true;
|
isChanged = true;
|
||||||
changes = this._addChange(bindingRecord, change, changes);
|
changes = this._addChange(bindingRecord, change, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proto.lastInDirective) {
|
|
||||||
if (isPresent(changes)) {
|
|
||||||
this._getDirectiveFor(directiveRecord.directiveIndex).onChange(changes);
|
|
||||||
changes = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (proto.lastInDirective) {
|
||||||
|
changes = null;
|
||||||
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
|
if (isChanged && bindingRecord.isOnPushChangeDetection()) {
|
||||||
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce();
|
||||||
}
|
}
|
||||||
|
@ -107,6 +114,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
isChanged = false;
|
isChanged = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.alreadyChecked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
callOnAllChangesDone() {
|
callOnAllChangesDone() {
|
||||||
|
@ -142,7 +151,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
|
|
||||||
_check(proto: ProtoRecord, throwOnChange: boolean): SimpleChange {
|
_check(proto: ProtoRecord, throwOnChange: boolean): SimpleChange {
|
||||||
try {
|
try {
|
||||||
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
|
if (proto.isPipeRecord()) {
|
||||||
return this._pipeCheck(proto, throwOnChange);
|
return this._pipeCheck(proto, throwOnChange);
|
||||||
} else {
|
} else {
|
||||||
return this._referenceCheck(proto, throwOnChange);
|
return this._referenceCheck(proto, throwOnChange);
|
||||||
|
|
|
@ -53,7 +53,8 @@ import {
|
||||||
RECORD_TYPE_BINDING_PIPE,
|
RECORD_TYPE_BINDING_PIPE,
|
||||||
RECORD_TYPE_INTERPOLATE,
|
RECORD_TYPE_INTERPOLATE,
|
||||||
RECORD_TYPE_SAFE_PROPERTY,
|
RECORD_TYPE_SAFE_PROPERTY,
|
||||||
RECORD_TYPE_SAFE_INVOKE_METHOD
|
RECORD_TYPE_SAFE_INVOKE_METHOD,
|
||||||
|
RECORD_TYPE_DIRECTIVE_LIFECYCLE
|
||||||
} from './proto_record';
|
} from './proto_record';
|
||||||
|
|
||||||
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||||
|
@ -72,7 +73,7 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||||
_createRecords(definition: ChangeDetectorDefinition) {
|
_createRecords(definition: ChangeDetectorDefinition) {
|
||||||
var recordBuilder = new ProtoRecordBuilder();
|
var recordBuilder = new ProtoRecordBuilder();
|
||||||
ListWrapper.forEach(definition.bindingRecords,
|
ListWrapper.forEach(definition.bindingRecords,
|
||||||
(b) => { recordBuilder.addAst(b, definition.variableNames); });
|
(b) => { recordBuilder.add(b, definition.variableNames); });
|
||||||
return coalesce(recordBuilder.records);
|
return coalesce(recordBuilder.records);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +92,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
|
||||||
_createFactory(definition: ChangeDetectorDefinition) {
|
_createFactory(definition: ChangeDetectorDefinition) {
|
||||||
var recordBuilder = new ProtoRecordBuilder();
|
var recordBuilder = new ProtoRecordBuilder();
|
||||||
ListWrapper.forEach(definition.bindingRecords,
|
ListWrapper.forEach(definition.bindingRecords,
|
||||||
(b) => { recordBuilder.addAst(b, definition.variableNames); });
|
(b) => { recordBuilder.add(b, definition.variableNames); });
|
||||||
var c = _jitProtoChangeDetectorClassCounter++;
|
var c = _jitProtoChangeDetectorClassCounter++;
|
||||||
var records = coalesce(recordBuilder.records);
|
var records = coalesce(recordBuilder.records);
|
||||||
var typeName = `ChangeDetector${c}`;
|
var typeName = `ChangeDetector${c}`;
|
||||||
|
@ -106,19 +107,30 @@ class ProtoRecordBuilder {
|
||||||
|
|
||||||
constructor() { this.records = []; }
|
constructor() { this.records = []; }
|
||||||
|
|
||||||
addAst(b: BindingRecord, variableNames: List<string> = null) {
|
add(b: BindingRecord, variableNames: List<string> = null) {
|
||||||
var oldLast = ListWrapper.last(this.records);
|
var oldLast = ListWrapper.last(this.records);
|
||||||
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
if (isPresent(oldLast) && oldLast.bindingRecord.directiveRecord == b.directiveRecord) {
|
||||||
oldLast.lastInDirective = false;
|
oldLast.lastInDirective = false;
|
||||||
}
|
}
|
||||||
|
this._appendRecords(b, variableNames);
|
||||||
_ConvertAstIntoProtoRecords.append(this.records, b, variableNames);
|
|
||||||
var newLast = ListWrapper.last(this.records);
|
var newLast = ListWrapper.last(this.records);
|
||||||
if (isPresent(newLast) && newLast !== oldLast) {
|
if (isPresent(newLast) && newLast !== oldLast) {
|
||||||
newLast.lastInBinding = true;
|
newLast.lastInBinding = true;
|
||||||
newLast.lastInDirective = 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 {
|
class _ConvertAstIntoProtoRecords {
|
||||||
|
|
|
@ -15,6 +15,7 @@ export const RECORD_TYPE_BINDING_PIPE = 9;
|
||||||
export const RECORD_TYPE_INTERPOLATE = 10;
|
export const RECORD_TYPE_INTERPOLATE = 10;
|
||||||
export const RECORD_TYPE_SAFE_PROPERTY = 11;
|
export const RECORD_TYPE_SAFE_PROPERTY = 11;
|
||||||
export const RECORD_TYPE_SAFE_INVOKE_METHOD = 12;
|
export const RECORD_TYPE_SAFE_INVOKE_METHOD = 12;
|
||||||
|
export const RECORD_TYPE_DIRECTIVE_LIFECYCLE = 13;
|
||||||
|
|
||||||
export class ProtoRecord {
|
export class ProtoRecord {
|
||||||
constructor(public mode: number, public name: string, public funcOrValue, public args: List<any>,
|
constructor(public mode: number, public name: string, public funcOrValue, public args: List<any>,
|
||||||
|
@ -26,4 +27,10 @@ export class ProtoRecord {
|
||||||
isPureFunction(): boolean {
|
isPureFunction(): boolean {
|
||||||
return this.mode === RECORD_TYPE_INTERPOLATE || this.mode === RECORD_TYPE_PRIMITIVE_OP;
|
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; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,7 @@ export {
|
||||||
Directive as DirectiveAnnotation,
|
Directive as DirectiveAnnotation,
|
||||||
onDestroy,
|
onDestroy,
|
||||||
onChange,
|
onChange,
|
||||||
|
onCheck,
|
||||||
|
onInit,
|
||||||
onAllChangesDone
|
onAllChangesDone
|
||||||
} from '../annotations_impl/annotations';
|
} from '../annotations_impl/annotations';
|
||||||
|
|
|
@ -1077,6 +1077,54 @@ export const onDestroy = CONST_EXPR(new LifecycleEvent("onDestroy"));
|
||||||
*/
|
*/
|
||||||
export const onChange = CONST_EXPR(new LifecycleEvent("onChange"));
|
export const onChange = CONST_EXPR(new LifecycleEvent("onChange"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify a directive when it has been checked.
|
||||||
|
*
|
||||||
|
* This method is called right after the directive's bindings have been checked,
|
||||||
|
* and before any of its children's bindings have been checked.
|
||||||
|
*
|
||||||
|
* It is invoked every time even when none of the directive's bindings has changed.
|
||||||
|
*
|
||||||
|
* ## Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* @Directive({
|
||||||
|
* selector: '[class-set]',
|
||||||
|
* lifecycle: [onCheck]
|
||||||
|
* })
|
||||||
|
* class ClassSet {
|
||||||
|
* onCheck() {
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @exportedAs angular2/annotations
|
||||||
|
*/
|
||||||
|
export const onCheck = CONST_EXPR(new LifecycleEvent("onCheck"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify a directive when it has been checked the first itme.
|
||||||
|
*
|
||||||
|
* This method is called right after the directive's bindings have been checked,
|
||||||
|
* and before any of its children's bindings have been checked.
|
||||||
|
*
|
||||||
|
* It is invoked only once.
|
||||||
|
*
|
||||||
|
* ## Example:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* @Directive({
|
||||||
|
* selector: '[class-set]',
|
||||||
|
* lifecycle: [onInit]
|
||||||
|
* })
|
||||||
|
* class ClassSet {
|
||||||
|
* onInit() {
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @exportedAs angular2/annotations
|
||||||
|
*/
|
||||||
|
export const onInit = CONST_EXPR(new LifecycleEvent("onInit"));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notify a directive when the bindings of all its children have been changed.
|
* Notify a directive when the bindings of all its children have been changed.
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,6 +19,12 @@ bool hasLifecycleHook(LifecycleEvent e, type, Directive annotation) {
|
||||||
|
|
||||||
} else if (e == onAllChangesDone) {
|
} else if (e == onAllChangesDone) {
|
||||||
interface = OnAllChangesDone;
|
interface = OnAllChangesDone;
|
||||||
|
|
||||||
|
} else if (e == onCheck) {
|
||||||
|
interface = OnCheck;
|
||||||
|
|
||||||
|
} else if (e == onInit) {
|
||||||
|
interface = OnInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
return interfaces.contains(interface);
|
return interfaces.contains(interface);
|
||||||
|
|
|
@ -26,6 +26,8 @@ import {
|
||||||
Component,
|
Component,
|
||||||
onChange,
|
onChange,
|
||||||
onDestroy,
|
onDestroy,
|
||||||
|
onCheck,
|
||||||
|
onInit,
|
||||||
onAllChangesDone
|
onAllChangesDone
|
||||||
} from 'angular2/src/core/annotations_impl/annotations';
|
} from 'angular2/src/core/annotations_impl/annotations';
|
||||||
import {hasLifecycleHook} from './directive_lifecycle_reflector';
|
import {hasLifecycleHook} from './directive_lifecycle_reflector';
|
||||||
|
@ -303,6 +305,8 @@ export class DirectiveBinding extends ResolvedBinding {
|
||||||
|
|
||||||
callOnDestroy: hasLifecycleHook(onDestroy, rb.key.token, ann),
|
callOnDestroy: hasLifecycleHook(onDestroy, rb.key.token, ann),
|
||||||
callOnChange: hasLifecycleHook(onChange, rb.key.token, ann),
|
callOnChange: hasLifecycleHook(onChange, rb.key.token, ann),
|
||||||
|
callOnCheck: hasLifecycleHook(onCheck, rb.key.token, ann),
|
||||||
|
callOnInit: hasLifecycleHook(onInit, rb.key.token, ann),
|
||||||
callOnAllChangesDone: hasLifecycleHook(onAllChangesDone, rb.key.token, ann),
|
callOnAllChangesDone: hasLifecycleHook(onAllChangesDone, rb.key.token, ann),
|
||||||
|
|
||||||
changeDetection: ann instanceof
|
changeDetection: ann instanceof
|
||||||
|
|
|
@ -11,6 +11,16 @@ export interface OnChange { onChange(changes: StringMap<string, any>): void; }
|
||||||
*/
|
*/
|
||||||
export interface OnDestroy { onDestroy(): void; }
|
export interface OnDestroy { onDestroy(): void; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines lifecycle method [onCheck] called when a directive is being checked.
|
||||||
|
*/
|
||||||
|
export interface OnCheck { onCheck(): void; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines lifecycle method [onInit] called when a directive is being checked the first time.
|
||||||
|
*/
|
||||||
|
export interface OnInit { onInit(): void; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines lifecycle method [onAllChangesDone ] called when the bindings of all its children have
|
* Defines lifecycle method [onAllChangesDone ] called when the bindings of all its children have
|
||||||
* been changed.
|
* been changed.
|
||||||
|
|
|
@ -85,17 +85,27 @@ class BindingRecordsCreator {
|
||||||
for (var i = 0; i < directiveBinders.length; i++) {
|
for (var i = 0; i < directiveBinders.length; i++) {
|
||||||
var directiveBinder = directiveBinders[i];
|
var directiveBinder = directiveBinders[i];
|
||||||
var directiveMetadata = allDirectiveMetadatas[directiveBinder.directiveIndex];
|
var directiveMetadata = allDirectiveMetadatas[directiveBinder.directiveIndex];
|
||||||
|
var directiveRecord = this._getDirectiveRecord(boundElementIndex, i, directiveMetadata);
|
||||||
|
|
||||||
// directive properties
|
// directive properties
|
||||||
MapWrapper.forEach(directiveBinder.propertyBindings, (astWithSource, propertyName) => {
|
MapWrapper.forEach(directiveBinder.propertyBindings, (astWithSource, propertyName) => {
|
||||||
// TODO: these setters should eventually be created by change detection, to make
|
// TODO: these setters should eventually be created by change detection, to make
|
||||||
// it monomorphic!
|
// it monomorphic!
|
||||||
var setter = reflector.setter(propertyName);
|
var setter = reflector.setter(propertyName);
|
||||||
var directiveRecord = this._getDirectiveRecord(boundElementIndex, i, directiveMetadata);
|
|
||||||
ListWrapper.push(bindings, BindingRecord.createForDirective(astWithSource, propertyName,
|
ListWrapper.push(bindings, BindingRecord.createForDirective(astWithSource, propertyName,
|
||||||
setter, directiveRecord));
|
setter, directiveRecord));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (directiveRecord.callOnChange) {
|
||||||
|
ListWrapper.push(bindings, BindingRecord.createDirectiveOnChange(directiveRecord));
|
||||||
|
}
|
||||||
|
if (directiveRecord.callOnInit) {
|
||||||
|
ListWrapper.push(bindings, BindingRecord.createDirectiveOnInit(directiveRecord));
|
||||||
|
}
|
||||||
|
if (directiveRecord.callOnCheck) {
|
||||||
|
ListWrapper.push(bindings, BindingRecord.createDirectiveOnCheck(directiveRecord));
|
||||||
|
}
|
||||||
|
|
||||||
// host properties
|
// host properties
|
||||||
MapWrapper.forEach(directiveBinder.hostPropertyBindings, (astWithSource, propertyName) => {
|
MapWrapper.forEach(directiveBinder.hostPropertyBindings, (astWithSource, propertyName) => {
|
||||||
var dirIndex = new DirectiveIndex(boundElementIndex, i);
|
var dirIndex = new DirectiveIndex(boundElementIndex, i);
|
||||||
|
@ -110,12 +120,14 @@ class BindingRecordsCreator {
|
||||||
var id = boundElementIndex * 100 + directiveIndex;
|
var id = boundElementIndex * 100 + directiveIndex;
|
||||||
|
|
||||||
if (!MapWrapper.contains(this._directiveRecordsMap, id)) {
|
if (!MapWrapper.contains(this._directiveRecordsMap, id)) {
|
||||||
var changeDetection = directiveMetadata.changeDetection;
|
MapWrapper.set(this._directiveRecordsMap, id, new DirectiveRecord({
|
||||||
|
directiveIndex: new DirectiveIndex(boundElementIndex, directiveIndex),
|
||||||
MapWrapper.set(this._directiveRecordsMap, id,
|
callOnAllChangesDone: directiveMetadata.callOnAllChangesDone,
|
||||||
new DirectiveRecord(new DirectiveIndex(boundElementIndex, directiveIndex),
|
callOnChange: directiveMetadata.callOnChange,
|
||||||
directiveMetadata.callOnAllChangesDone,
|
callOnCheck: directiveMetadata.callOnCheck,
|
||||||
directiveMetadata.callOnChange, changeDetection));
|
callOnInit: directiveMetadata.callOnInit,
|
||||||
|
changeDetection: directiveMetadata.changeDetection
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MapWrapper.get(this._directiveRecordsMap, id);
|
return MapWrapper.get(this._directiveRecordsMap, id);
|
||||||
|
|
|
@ -196,6 +196,10 @@ dynamic normalizeBlank(obj) {
|
||||||
return isBlank(obj) ? null : obj;
|
return isBlank(obj) ? null : obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool normalizeBool(bool obj) {
|
||||||
|
return isBlank(obj) ? false : obj;
|
||||||
|
}
|
||||||
|
|
||||||
bool isJsObject(o) {
|
bool isJsObject(o) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,6 +231,10 @@ export function normalizeBlank(obj) {
|
||||||
return isBlank(obj) ? null : obj;
|
return isBlank(obj) ? null : obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeBool(obj:boolean):boolean {
|
||||||
|
return isBlank(obj) ? false : obj;
|
||||||
|
}
|
||||||
|
|
||||||
export function isJsObject(o): boolean {
|
export function isJsObject(o): boolean {
|
||||||
return o !== null && (typeof o === "function" || typeof o === "object");
|
return o !== null && (typeof o === "function" || typeof o === "object");
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,11 +138,13 @@ export class DirectiveMetadata {
|
||||||
type: number;
|
type: number;
|
||||||
callOnDestroy: boolean;
|
callOnDestroy: boolean;
|
||||||
callOnChange: boolean;
|
callOnChange: boolean;
|
||||||
|
callOnCheck: boolean;
|
||||||
|
callOnInit: boolean;
|
||||||
callOnAllChangesDone: boolean;
|
callOnAllChangesDone: boolean;
|
||||||
changeDetection: string;
|
changeDetection: string;
|
||||||
constructor({id, selector, compileChildren, events, hostListeners, hostProperties, hostAttributes,
|
constructor({id, selector, compileChildren, events, hostListeners, hostProperties, hostAttributes,
|
||||||
hostActions, properties, readAttributes, type, callOnDestroy, callOnChange,
|
hostActions, properties, readAttributes, type, callOnDestroy, callOnChange,
|
||||||
callOnAllChangesDone, changeDetection}: {
|
callOnCheck, callOnInit, callOnAllChangesDone, changeDetection}: {
|
||||||
id?: string,
|
id?: string,
|
||||||
selector?: string,
|
selector?: string,
|
||||||
compileChildren?: boolean,
|
compileChildren?: boolean,
|
||||||
|
@ -156,6 +158,8 @@ export class DirectiveMetadata {
|
||||||
type?: number,
|
type?: number,
|
||||||
callOnDestroy?: boolean,
|
callOnDestroy?: boolean,
|
||||||
callOnChange?: boolean,
|
callOnChange?: boolean,
|
||||||
|
callOnCheck?: boolean,
|
||||||
|
callOnInit?: boolean,
|
||||||
callOnAllChangesDone?: boolean,
|
callOnAllChangesDone?: boolean,
|
||||||
changeDetection?: string
|
changeDetection?: string
|
||||||
}) {
|
}) {
|
||||||
|
@ -172,6 +176,8 @@ export class DirectiveMetadata {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.callOnDestroy = callOnDestroy;
|
this.callOnDestroy = callOnDestroy;
|
||||||
this.callOnChange = callOnChange;
|
this.callOnChange = callOnChange;
|
||||||
|
this.callOnCheck = callOnCheck;
|
||||||
|
this.callOnInit = callOnInit;
|
||||||
this.callOnAllChangesDone = callOnAllChangesDone;
|
this.callOnAllChangesDone = callOnAllChangesDone;
|
||||||
this.changeDetection = changeDetection;
|
this.changeDetection = changeDetection;
|
||||||
}
|
}
|
||||||
|
|
|
@ -311,10 +311,26 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updating directives", () => {
|
describe("updating directives", () => {
|
||||||
var dirRecord1 = new DirectiveRecord(new DirectiveIndex(0, 0), true, true, DEFAULT);
|
var dirRecord1 = new DirectiveRecord({
|
||||||
var dirRecord2 = new DirectiveRecord(new DirectiveIndex(0, 1), true, true, DEFAULT);
|
directiveIndex: new DirectiveIndex(0, 0),
|
||||||
var dirRecordNoCallbacks =
|
callOnChange: true,
|
||||||
new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT);
|
callOnCheck: true,
|
||||||
|
callOnAllChangesDone: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var dirRecord2 = new DirectiveRecord({
|
||||||
|
directiveIndex: new DirectiveIndex(0, 1),
|
||||||
|
callOnChange: true,
|
||||||
|
callOnCheck: true,
|
||||||
|
callOnAllChangesDone: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var dirRecordNoCallbacks = new DirectiveRecord({
|
||||||
|
directiveIndex: new DirectiveIndex(0, 0),
|
||||||
|
callOnChange: false,
|
||||||
|
callOnCheck: false,
|
||||||
|
callOnAllChangesDone: false
|
||||||
|
});
|
||||||
|
|
||||||
function updateA(exp: string, dirRecord) {
|
function updateA(exp: string, dirRecord) {
|
||||||
return BindingRecord.createForDirective(ast(exp), "a", (o, v) => o.a = v,
|
return BindingRecord.createForDirective(ast(exp), "a", (o, v) => o.a = v,
|
||||||
|
@ -353,7 +369,9 @@ export function main() {
|
||||||
var pcd = createProtoChangeDetector([
|
var pcd = createProtoChangeDetector([
|
||||||
updateA("1", dirRecord1),
|
updateA("1", dirRecord1),
|
||||||
updateB("2", dirRecord1),
|
updateB("2", dirRecord1),
|
||||||
updateA("3", dirRecord2)
|
BindingRecord.createDirectiveOnChange(dirRecord1),
|
||||||
|
updateA("3", dirRecord2),
|
||||||
|
BindingRecord.createDirectiveOnChange(dirRecord2)
|
||||||
],
|
],
|
||||||
[], [dirRecord1, dirRecord2]);
|
[], [dirRecord1, dirRecord2]);
|
||||||
|
|
||||||
|
@ -366,10 +384,13 @@ export function main() {
|
||||||
expect(directive1.changes).toEqual({'a': 1, 'b': 2});
|
expect(directive1.changes).toEqual({'a': 1, 'b': 2});
|
||||||
expect(directive2.changes).toEqual({'a': 3});
|
expect(directive2.changes).toEqual({'a': 3});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("onCheck", () => {
|
||||||
|
it("should notify the directive when it is checked", () => {
|
||||||
|
var pcd = createProtoChangeDetector(
|
||||||
|
[BindingRecord.createDirectiveOnCheck(dirRecord1)], [], [dirRecord1]);
|
||||||
|
|
||||||
it("should not call onChange when callOnChange is false", () => {
|
|
||||||
var pcd = createProtoChangeDetector([updateA("1", dirRecordNoCallbacks)], [],
|
|
||||||
[dirRecordNoCallbacks]);
|
|
||||||
|
|
||||||
var cd = pcd.instantiate(dispatcher);
|
var cd = pcd.instantiate(dispatcher);
|
||||||
|
|
||||||
|
@ -377,7 +398,63 @@ export function main() {
|
||||||
|
|
||||||
cd.detectChanges();
|
cd.detectChanges();
|
||||||
|
|
||||||
expect(directive1.changes).toEqual(null);
|
expect(directive1.onCheckCalled).toBe(true);
|
||||||
|
|
||||||
|
directive1.onCheckCalled = false;
|
||||||
|
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
expect(directive1.onCheckCalled).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not call onCheck in detectNoChanges", () => {
|
||||||
|
var pcd = createProtoChangeDetector(
|
||||||
|
[BindingRecord.createDirectiveOnCheck(dirRecord1)], [], [dirRecord1]);
|
||||||
|
|
||||||
|
|
||||||
|
var cd = pcd.instantiate(dispatcher);
|
||||||
|
|
||||||
|
cd.hydrate(null, null, dirs([directive1]));
|
||||||
|
|
||||||
|
cd.checkNoChanges();
|
||||||
|
|
||||||
|
expect(directive1.onCheckCalled).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("onInit", () => {
|
||||||
|
it("should notify the directive after it has been checked the first time", () => {
|
||||||
|
var pcd = createProtoChangeDetector(
|
||||||
|
[BindingRecord.createDirectiveOnInit(dirRecord1)], [], [dirRecord1]);
|
||||||
|
|
||||||
|
|
||||||
|
var cd = pcd.instantiate(dispatcher);
|
||||||
|
|
||||||
|
cd.hydrate(null, null, dirs([directive1]));
|
||||||
|
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
expect(directive1.onInitCalled).toBe(true);
|
||||||
|
|
||||||
|
directive1.onInitCalled = false;
|
||||||
|
|
||||||
|
cd.detectChanges();
|
||||||
|
|
||||||
|
expect(directive1.onInitCalled).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not call onInit in detectNoChanges", () => {
|
||||||
|
var pcd = createProtoChangeDetector(
|
||||||
|
[BindingRecord.createDirectiveOnInit(dirRecord1)], [], [dirRecord1]);
|
||||||
|
|
||||||
|
|
||||||
|
var cd = pcd.instantiate(dispatcher);
|
||||||
|
|
||||||
|
cd.hydrate(null, null, dirs([directive1]));
|
||||||
|
|
||||||
|
cd.checkNoChanges();
|
||||||
|
|
||||||
|
expect(directive1.onInitCalled).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -466,7 +543,7 @@ export function main() {
|
||||||
|
|
||||||
describe("reading directives", () => {
|
describe("reading directives", () => {
|
||||||
var index = new DirectiveIndex(0, 0);
|
var index = new DirectiveIndex(0, 0);
|
||||||
var dirRecord = new DirectiveRecord(index, false, false, DEFAULT);
|
var dirRecord = new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0)});
|
||||||
|
|
||||||
it("should read directive properties", () => {
|
it("should read directive properties", () => {
|
||||||
var directive = new TestDirective();
|
var directive = new TestDirective();
|
||||||
|
@ -688,8 +765,8 @@ export function main() {
|
||||||
checkedDetector.mode = CHECKED;
|
checkedDetector.mode = CHECKED;
|
||||||
|
|
||||||
// this directive is a component with ON_PUSH change detection
|
// this directive is a component with ON_PUSH change detection
|
||||||
dirRecordWithOnPush =
|
dirRecordWithOnPush = new DirectiveRecord(
|
||||||
new DirectiveRecord(new DirectiveIndex(0, 0), false, false, ON_PUSH);
|
{directiveIndex: new DirectiveIndex(0, 0), changeDetection: ON_PUSH});
|
||||||
|
|
||||||
// a record updating a component
|
// a record updating a component
|
||||||
updateDirWithOnPushRecord = BindingRecord.createForDirective(
|
updateDirWithOnPushRecord = BindingRecord.createForDirective(
|
||||||
|
@ -943,15 +1020,23 @@ class TestDirective {
|
||||||
changes;
|
changes;
|
||||||
onChangesDoneCalled;
|
onChangesDoneCalled;
|
||||||
onChangesDoneSpy;
|
onChangesDoneSpy;
|
||||||
|
onCheckCalled;
|
||||||
|
onInitCalled;
|
||||||
|
|
||||||
constructor(onChangesDoneSpy = null) {
|
constructor(onChangesDoneSpy = null) {
|
||||||
this.onChangesDoneCalled = false;
|
this.onChangesDoneCalled = false;
|
||||||
|
this.onCheckCalled = false;
|
||||||
|
this.onInitCalled = false;
|
||||||
this.onChangesDoneSpy = onChangesDoneSpy;
|
this.onChangesDoneSpy = onChangesDoneSpy;
|
||||||
this.a = null;
|
this.a = null;
|
||||||
this.b = null;
|
this.b = null;
|
||||||
this.changes = null;
|
this.changes = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCheck() { this.onCheckCalled = true; }
|
||||||
|
|
||||||
|
onInit() { this.onInitCalled = true; }
|
||||||
|
|
||||||
onChange(changes) {
|
onChange(changes) {
|
||||||
var r = {};
|
var r = {};
|
||||||
StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue);
|
StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue);
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
|
||||||
|
|
||||||
import {coalesce} from 'angular2/src/change_detection/coalesce';
|
import {coalesce} from 'angular2/src/change_detection/coalesce';
|
||||||
import {RECORD_TYPE_SELF, ProtoRecord} from 'angular2/src/change_detection/proto_record';
|
import {
|
||||||
|
RECORD_TYPE_SELF,
|
||||||
|
RECORD_TYPE_DIRECTIVE_LIFECYCLE,
|
||||||
|
ProtoRecord
|
||||||
|
} from 'angular2/src/change_detection/proto_record';
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false) {
|
function r(funcOrValue, args, contextIndex, selfIndex, lastInBinding = false, mode = 99) {
|
||||||
return new ProtoRecord(99, "name", funcOrValue, args, null, contextIndex, null, selfIndex, null,
|
return new ProtoRecord(mode, "name", funcOrValue, args, null, contextIndex, null, selfIndex,
|
||||||
null, lastInBinding, false);
|
null, null, lastInBinding, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("change detection - coalesce", () => {
|
describe("change detection - coalesce", () => {
|
||||||
|
@ -54,5 +58,14 @@ export function main() {
|
||||||
expect(rs[1]).toEqual(new ProtoRecord(RECORD_TYPE_SELF, "self", null, [], null, 1, null, 2,
|
expect(rs[1]).toEqual(new ProtoRecord(RECORD_TYPE_SELF, "self", null, [], null, 1, null, 2,
|
||||||
null, null, true, false));
|
null, null, true, false));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should not coalesce directive lifecycle records", () => {
|
||||||
|
var rs = coalesce([
|
||||||
|
r("onCheck", [], 0, 1, true, RECORD_TYPE_DIRECTIVE_LIFECYCLE),
|
||||||
|
r("onCheck", [], 0, 1, true, RECORD_TYPE_DIRECTIVE_LIFECYCLE)
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(rs.length).toEqual(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,34 @@ main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("onCheck", () {
|
||||||
|
it("should be true when the directive implements OnCheck", () {
|
||||||
|
expect(metadata(DirectiveImplementingOnCheck, new Directive()).callOnCheck).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true when the lifecycle includes onCheck", () {
|
||||||
|
expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onCheck])).callOnCheck).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false otherwise", () {
|
||||||
|
expect(metadata(DirectiveNoHooks, new Directive()).callOnCheck).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("onInit", () {
|
||||||
|
it("should be true when the directive implements OnInit", () {
|
||||||
|
expect(metadata(DirectiveImplementingOnInit, new Directive()).callOnInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true when the lifecycle includes onInit", () {
|
||||||
|
expect(metadata(DirectiveNoHooks, new Directive(lifecycle: [onInit])).callOnInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false otherwise", () {
|
||||||
|
expect(metadata(DirectiveNoHooks, new Directive()).callOnInit).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("onAllChangesDone", () {
|
describe("onAllChangesDone", () {
|
||||||
it("should be true when the directive implements OnAllChangesDone", () {
|
it("should be true when the directive implements OnAllChangesDone", () {
|
||||||
expect(metadata(DirectiveImplementingOnAllChangesDone, new Directive()).callOnAllChangesDone).toBe(true);
|
expect(metadata(DirectiveImplementingOnAllChangesDone, new Directive()).callOnAllChangesDone).toBe(true);
|
||||||
|
@ -66,6 +94,14 @@ class DirectiveImplementingOnChange implements OnChange {
|
||||||
onChange(_){}
|
onChange(_){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DirectiveImplementingOnCheck implements OnCheck {
|
||||||
|
onCheck(){}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectiveImplementingOnInit implements OnInit {
|
||||||
|
onInit(){}
|
||||||
|
}
|
||||||
|
|
||||||
class DirectiveImplementingOnDestroy implements OnDestroy {
|
class DirectiveImplementingOnDestroy implements OnDestroy {
|
||||||
onDestroy(){}
|
onDestroy(){}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ import {
|
||||||
Directive,
|
Directive,
|
||||||
onChange,
|
onChange,
|
||||||
onDestroy,
|
onDestroy,
|
||||||
|
onCheck,
|
||||||
|
onInit,
|
||||||
onAllChangesDone
|
onAllChangesDone
|
||||||
} from 'angular2/src/core/annotations_impl/annotations';
|
} from 'angular2/src/core/annotations_impl/annotations';
|
||||||
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
|
import {DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
|
||||||
|
@ -64,6 +66,34 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("onInit", () => {
|
||||||
|
it("should be true when the directive has the onInit method", () => {
|
||||||
|
expect(metadata(DirectiveWithOnInitMethod, new Directive({})).callOnInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true when the lifecycle includes onDestroy", () => {
|
||||||
|
expect(metadata(DirectiveNoHooks, new Directive({lifecycle: [onInit]})).callOnInit)
|
||||||
|
.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false otherwise",
|
||||||
|
() => { expect(metadata(DirectiveNoHooks, new Directive()).callOnInit).toBe(false); });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("onCheck", () => {
|
||||||
|
it("should be true when the directive has the onCheck method", () => {
|
||||||
|
expect(metadata(DirectiveWithOnCheckMethod, new Directive({})).callOnCheck).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be true when the lifecycle includes onCheck", () => {
|
||||||
|
expect(metadata(DirectiveNoHooks, new Directive({lifecycle: [onCheck]})).callOnCheck)
|
||||||
|
.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should be false otherwise",
|
||||||
|
() => { expect(metadata(DirectiveNoHooks, new Directive()).callOnCheck).toBe(false); });
|
||||||
|
});
|
||||||
|
|
||||||
describe("onAllChangesDone", () => {
|
describe("onAllChangesDone", () => {
|
||||||
it("should be true when the directive has the onAllChangesDone method", () => {
|
it("should be true when the directive has the onAllChangesDone method", () => {
|
||||||
expect(
|
expect(
|
||||||
|
@ -91,10 +121,18 @@ class DirectiveWithOnChangeMethod {
|
||||||
onChange(_) {}
|
onChange(_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DirectiveWithOnInitMethod {
|
||||||
|
onInit() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectiveWithOnCheckMethod {
|
||||||
|
onCheck() {}
|
||||||
|
}
|
||||||
|
|
||||||
class DirectiveWithOnDestroyMethod {
|
class DirectiveWithOnDestroyMethod {
|
||||||
onDestroy(_) {}
|
onDestroy(_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectiveWithOnAllChangesDoneMethod {
|
class DirectiveWithOnAllChangesDoneMethod {
|
||||||
onAllChangesDone(_) {}
|
onAllChangesDone() {}
|
||||||
}
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {
|
||||||
|
AsyncTestCompleter,
|
||||||
|
beforeEach,
|
||||||
|
ddescribe,
|
||||||
|
describe,
|
||||||
|
expect,
|
||||||
|
iit,
|
||||||
|
inject,
|
||||||
|
it,
|
||||||
|
xdescribe,
|
||||||
|
xit,
|
||||||
|
IS_DARTIUM
|
||||||
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
|
import {TestBed} from 'angular2/src/test_lib/test_bed';
|
||||||
|
import {Directive, Component, View, onCheck, onInit, onChange} from 'angular2/angular2';
|
||||||
|
import * as viewAnn from 'angular2/src/core/annotations_impl/view';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe('directive lifecycle integration spec', () => {
|
||||||
|
var ctx;
|
||||||
|
|
||||||
|
beforeEach(() => { ctx = new MyComp(); });
|
||||||
|
|
||||||
|
it('should invoke lifecycle methods onChanges > onInit > onCheck',
|
||||||
|
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
tb.overrideView(
|
||||||
|
MyComp,
|
||||||
|
new viewAnn.View(
|
||||||
|
{template: '<div [field]="123" [lifecycle]></div>', directives: [LifecycleDir]}));
|
||||||
|
|
||||||
|
tb.createView(MyComp, {context: ctx})
|
||||||
|
.then((view) => {
|
||||||
|
var dir = view.rawView.elementInjectors[0].get(LifecycleDir);
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
expect(dir.log).toEqual(["onChanges", "onInit", "onCheck"]);
|
||||||
|
|
||||||
|
view.detectChanges();
|
||||||
|
|
||||||
|
expect(dir.log).toEqual(["onChanges", "onInit", "onCheck", "onCheck"]);
|
||||||
|
|
||||||
|
async.done();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: "[lifecycle]",
|
||||||
|
properties: {'field': 'field'},
|
||||||
|
lifecycle: [onChange, onCheck, onInit]
|
||||||
|
})
|
||||||
|
class LifecycleDir {
|
||||||
|
field;
|
||||||
|
log: List<string>;
|
||||||
|
|
||||||
|
constructor() { this.log = []; }
|
||||||
|
|
||||||
|
onChange(_) { ListWrapper.push(this.log, "onChanges"); }
|
||||||
|
|
||||||
|
onInit() { ListWrapper.push(this.log, "onInit"); }
|
||||||
|
|
||||||
|
onCheck() { ListWrapper.push(this.log, "onCheck"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'my-comp'})
|
||||||
|
@View({directives: []})
|
||||||
|
class MyComp {
|
||||||
|
}
|
|
@ -191,7 +191,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec
|
||||||
var parentProto = changeDetection.createProtoChangeDetector(new ChangeDetectorDefinition('parent', null, [], [], []));
|
var parentProto = changeDetection.createProtoChangeDetector(new ChangeDetectorDefinition('parent', null, [], [], []));
|
||||||
var parentCd = parentProto.instantiate(dispatcher);
|
var parentCd = parentProto.instantiate(dispatcher);
|
||||||
|
|
||||||
var directiveRecord = new DirectiveRecord(new DirectiveIndex(0, 0), false, false, DEFAULT);
|
var directiveRecord = new DirectiveRecord({directiveIndex: new DirectiveIndex(0, 0)});
|
||||||
var bindings = [
|
var bindings = [
|
||||||
BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord),
|
BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord),
|
||||||
BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord),
|
BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord),
|
||||||
|
|
Loading…
Reference in New Issue