feat(change_detection): updated change detection to update directive directly, without the dispatcher
This commit is contained in:
parent
50098767fc
commit
69c3bff086
|
@ -5,6 +5,6 @@ class ChangeDetectorJITGenerator {
|
|||
}
|
||||
|
||||
generate() {
|
||||
throw "Not supported in Dart";
|
||||
throw "Jit Change Detection is not supported in Dart";
|
||||
}
|
||||
}
|
|
@ -76,16 +76,23 @@ function pipeOnDestroyTemplate(pipeNames:List) {
|
|||
return pipeNames.map((p) => `${p}.onDestroy()`).join("\n");
|
||||
}
|
||||
|
||||
function hydrateTemplate(type:string, mode:string, fieldsDefinitions:string, pipeOnDestroy:string):string {
|
||||
function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipeOnDestroy:string,
|
||||
directiveFieldNames:List<String>):string {
|
||||
var directiveInit = "";
|
||||
for(var i = 0; i < directiveFieldNames.length; ++i) {
|
||||
directiveInit += `${directiveFieldNames[i]} = this.directiveMementos[${i}].directive(directives);\n`;
|
||||
}
|
||||
|
||||
return `
|
||||
${type}.prototype.hydrate = function(context, locals) {
|
||||
${type}.prototype.hydrate = function(context, locals, directives) {
|
||||
${MODE_ACCESSOR} = "${mode}";
|
||||
${CONTEXT_ACCESSOR} = context;
|
||||
${LOCALS_ACCESSOR} = locals;
|
||||
${directiveInit}
|
||||
}
|
||||
${type}.prototype.dehydrate = function() {
|
||||
${pipeOnDestroy}
|
||||
${fieldsDefinitions}
|
||||
${fieldDefinitions}
|
||||
${LOCALS_ACCESSOR} = null;
|
||||
}
|
||||
${type}.prototype.hydrated = function() {
|
||||
|
@ -110,8 +117,8 @@ ${type}.prototype.callOnAllChangesDone = function() {
|
|||
`;
|
||||
}
|
||||
|
||||
function onAllChangesDoneTemplate(index:number):string {
|
||||
return `${DISPATCHER_ACCESSOR}.onAllChangesDone(${MEMENTOS_ACCESSOR}[${index}]);`;
|
||||
function onAllChangesDoneTemplate(directive:string):string {
|
||||
return `${directive}.onAllChangesDone();`;
|
||||
}
|
||||
|
||||
|
||||
|
@ -130,7 +137,7 @@ ${records}
|
|||
}
|
||||
|
||||
function pipeCheckTemplate(protoIndex:number, context:string, bindingPropagationConfig:string, pipe:string, pipeType:string,
|
||||
oldValue:string, newValue:string, change:string, invokeMementoAndAddChange:string,
|
||||
oldValue:string, newValue:string, change:string, invokeMemento:string,
|
||||
addToChanges, lastInDirective:string):string{
|
||||
return `
|
||||
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
|
||||
|
@ -144,7 +151,7 @@ if (${pipe} === ${UTIL}.unitialized()) {
|
|||
${newValue} = ${pipe}.transform(${context});
|
||||
if (! ${UTIL}.noChangeMarker(${newValue})) {
|
||||
${change} = true;
|
||||
${invokeMementoAndAddChange}
|
||||
${invokeMemento}
|
||||
${addToChanges}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
|
@ -153,13 +160,13 @@ ${lastInDirective}
|
|||
}
|
||||
|
||||
function referenceCheckTemplate(protoIndex:number, assignment:string, oldValue:string, newValue:string, change:string,
|
||||
invokeMementoAndAddChange:string, addToChanges:string, lastInDirective:string):string {
|
||||
invokeMemento:string, addToChanges:string, lastInDirective:string):string {
|
||||
return `
|
||||
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
|
||||
${assignment}
|
||||
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
|
||||
${change} = true;
|
||||
${invokeMementoAndAddChange}
|
||||
${invokeMemento}
|
||||
${addToChanges}
|
||||
${oldValue} = ${newValue};
|
||||
}
|
||||
|
@ -196,20 +203,26 @@ function addToChangesTemplate(oldValue:string, newValue:string):string {
|
|||
return `${CHANGES_LOCAL} = ${UTIL}.addChange(${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingMemento, ${UTIL}.simpleChange(${oldValue}, ${newValue}));`;
|
||||
}
|
||||
|
||||
function invokeBindingMemento(oldValue:string, newValue:string):string {
|
||||
function updateDirectiveTemplate(oldValue:string, newValue:string, directiveProperty:string):string {
|
||||
return `
|
||||
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
|
||||
${directiveProperty} = ${newValue};
|
||||
`;
|
||||
}
|
||||
|
||||
function updateElementTemplate(oldValue:string, newValue:string):string {
|
||||
return `
|
||||
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
|
||||
${DISPATCHER_ACCESSOR}.invokeMementoFor(${CURRENT_PROTO}.bindingMemento, ${newValue});
|
||||
`;
|
||||
}
|
||||
|
||||
function lastInDirectiveTemplate(protoIndex:number):string{
|
||||
function notifyOnChangesTemplate(directive:string):string{
|
||||
return `
|
||||
if (${CHANGES_LOCAL}) {
|
||||
${DISPATCHER_ACCESSOR}.onChange(${PROTOS_ACCESSOR}[${protoIndex}].directiveMemento, ${CHANGES_LOCAL});
|
||||
if(${CHANGES_LOCAL}) {
|
||||
${directive}.onChange(${CHANGES_LOCAL});
|
||||
${CHANGES_LOCAL} = null;
|
||||
}
|
||||
${CHANGES_LOCAL} = null;
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -271,13 +284,22 @@ export class ChangeDetectorJITGenerator {
|
|||
genHydrate():string {
|
||||
var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy);
|
||||
return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(),
|
||||
pipeOnDestroyTemplate(this.getNonNullPipeNames()));
|
||||
pipeOnDestroyTemplate(this.getNonNullPipeNames()), this.getDirectiveFieldNames());
|
||||
}
|
||||
|
||||
getDirectiveFieldNames():List<string> {
|
||||
return this.directiveMementos.map((d) => this.getDirective(d));
|
||||
}
|
||||
|
||||
getDirective(memento) {
|
||||
return `this.directive_${memento.name}`;
|
||||
}
|
||||
|
||||
genFieldDefinitions() {
|
||||
var fields = [];
|
||||
fields = fields.concat(this.fieldNames);
|
||||
fields = fields.concat(this.getNonNullPipeNames());
|
||||
fields = fields.concat(this.getDirectiveFieldNames());
|
||||
return fieldDefinitionsTemplate(fields);
|
||||
}
|
||||
|
||||
|
@ -303,7 +325,8 @@ export class ChangeDetectorJITGenerator {
|
|||
for (var i = mementos.length - 1; i >= 0; --i) {
|
||||
var memento = mementos[i];
|
||||
if (memento.callOnAllChangesDone) {
|
||||
notifications.push(onAllChangesDoneTemplate(i));
|
||||
var directive = `this.directive_${memento.name}`;
|
||||
notifications.push(onAllChangesDoneTemplate(directive));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,9 +363,9 @@ export class ChangeDetectorJITGenerator {
|
|||
var pipe = this.pipeNames[r.selfIndex];
|
||||
var bpc = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.bindingPropagationConfig" : "null";
|
||||
|
||||
var invokeMemento = this.getInvokeMementoAndAddChangeTemplate(r);
|
||||
var invokeMemento = this.genUpdateDirectiveOrElement(r);
|
||||
var addToChanges = this.genAddToChanges(r);
|
||||
var lastInDirective = this.genLastInDirective(r);
|
||||
var lastInDirective = this.genNotifyOnChanges(r);
|
||||
|
||||
return pipeCheckTemplate(r.selfIndex - 1, context, bpc, pipe, r.name, oldValue, newValue, change,
|
||||
invokeMemento, addToChanges, lastInDirective);
|
||||
|
@ -354,9 +377,9 @@ export class ChangeDetectorJITGenerator {
|
|||
var change = this.changeNames[r.selfIndex];
|
||||
var assignment = this.genUpdateCurrentValue(r);
|
||||
|
||||
var invokeMemento = this.getInvokeMementoAndAddChangeTemplate(r);
|
||||
var invokeMemento = this.genUpdateDirectiveOrElement(r);
|
||||
var addToChanges = this.genAddToChanges(r);
|
||||
var lastInDirective = this.genLastInDirective(r);
|
||||
var lastInDirective = this.genNotifyOnChanges(r);
|
||||
|
||||
var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change,
|
||||
invokeMemento, addToChanges, lastInDirective);
|
||||
|
@ -426,10 +449,18 @@ export class ChangeDetectorJITGenerator {
|
|||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
getInvokeMementoAndAddChangeTemplate(r:ProtoRecord):string {
|
||||
genUpdateDirectiveOrElement(r:ProtoRecord):string {
|
||||
if (! r.lastInBinding) return "";
|
||||
|
||||
var newValue = this.localNames[r.selfIndex];
|
||||
var oldValue = this.fieldNames[r.selfIndex];
|
||||
return r.lastInBinding ? invokeBindingMemento(oldValue, newValue) : "";
|
||||
|
||||
if (isPresent(r.directiveMemento)) {
|
||||
var directiveProperty = `${this.getDirective(r.directiveMemento)}.${r.bindingMemento.propertyName}`;
|
||||
return updateDirectiveTemplate(oldValue, newValue, directiveProperty);
|
||||
} else {
|
||||
return updateElementTemplate(oldValue, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
genAddToChanges(r:ProtoRecord):string {
|
||||
|
@ -439,9 +470,13 @@ export class ChangeDetectorJITGenerator {
|
|||
return callOnChange ? addToChangesTemplate(oldValue, newValue) : "";
|
||||
}
|
||||
|
||||
genLastInDirective(r:ProtoRecord):string{
|
||||
genNotifyOnChanges(r:ProtoRecord):string{
|
||||
var callOnChange = r.directiveMemento && r.directiveMemento.callOnChange;
|
||||
return r.lastInDirective && callOnChange ? lastInDirectiveTemplate(r.selfIndex - 1) : '';
|
||||
if (r.lastInDirective && callOnChange) {
|
||||
return notifyOnChangesTemplate(this.getDirective(r.directiveMemento));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
genArgs(r:ProtoRecord):string {
|
||||
|
|
|
@ -34,6 +34,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
prevContexts:List;
|
||||
|
||||
protos:List<ProtoRecord>;
|
||||
directives:any;
|
||||
directiveMementos:List;
|
||||
changeControlStrategy:string;
|
||||
|
||||
|
@ -53,16 +54,18 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
ListWrapper.fill(this.prevContexts, uninitialized);
|
||||
ListWrapper.fill(this.changes, false);
|
||||
this.locals = null;
|
||||
this.directives = null;
|
||||
|
||||
this.protos = protoRecords;
|
||||
this.directiveMementos = directiveMementos;
|
||||
this.changeControlStrategy = changeControlStrategy;
|
||||
}
|
||||
|
||||
hydrate(context:any, locals:any) {
|
||||
hydrate(context:any, locals:any, directives:any) {
|
||||
this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy);
|
||||
this.values[0] = context;
|
||||
this.locals = locals;
|
||||
this.directives = directives;
|
||||
}
|
||||
|
||||
dehydrate() {
|
||||
|
@ -90,29 +93,18 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
var protos:List<ProtoRecord> = this.protos;
|
||||
|
||||
var changes = null;
|
||||
var currentDirectiveMemento = null;
|
||||
|
||||
for (var i = 0; i < protos.length; ++i) {
|
||||
var proto:ProtoRecord = protos[i];
|
||||
if (isBlank(currentDirectiveMemento)) {
|
||||
currentDirectiveMemento = proto.directiveMemento;
|
||||
}
|
||||
|
||||
var change = this._check(proto);
|
||||
if (isPresent(change)) {
|
||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||
this.dispatcher.invokeMementoFor(proto.bindingMemento, change.currentValue);
|
||||
|
||||
if (isPresent(currentDirectiveMemento) && currentDirectiveMemento.callOnChange) {
|
||||
changes = ChangeDetectionUtil.addChange(changes, proto.bindingMemento, change);
|
||||
}
|
||||
this._updateDirectiveOrElement(change, proto.directiveMemento, proto.bindingMemento);
|
||||
changes = this._addChange(proto.directiveMemento, proto.bindingMemento, change, changes);
|
||||
}
|
||||
|
||||
if (proto.lastInDirective) {
|
||||
if (isPresent(changes)) {
|
||||
this.dispatcher.onChange(currentDirectiveMemento, changes);
|
||||
}
|
||||
currentDirectiveMemento = null;
|
||||
if (proto.lastInDirective && isPresent(changes)) {
|
||||
this._directive(proto.directiveMemento).onChange(changes);
|
||||
changes = null;
|
||||
}
|
||||
}
|
||||
|
@ -123,11 +115,31 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
for (var i = mementos.length - 1; i >= 0; --i) {
|
||||
var memento = mementos[i];
|
||||
if (memento.callOnAllChangesDone) {
|
||||
this.dispatcher.onAllChangesDone(memento);
|
||||
this._directive(memento).onAllChangesDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_updateDirectiveOrElement(change, directiveMemento, bindingMemento) {
|
||||
if (isBlank(directiveMemento)) {
|
||||
this.dispatcher.invokeMementoFor(bindingMemento, change.currentValue);
|
||||
} else {
|
||||
bindingMemento.setter(this._directive(directiveMemento), change.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
_addChange(directiveMemento, bindingMemento, change, changes) {
|
||||
if (isPresent(directiveMemento) && directiveMemento.callOnChange) {
|
||||
return ChangeDetectionUtil.addChange(changes, bindingMemento, change);
|
||||
} else {
|
||||
return changes;
|
||||
}
|
||||
}
|
||||
|
||||
_directive(memento) {
|
||||
return memento.directive(this.directives);
|
||||
}
|
||||
|
||||
_check(proto:ProtoRecord) {
|
||||
try {
|
||||
if (proto.mode === RECORD_TYPE_PIPE || proto.mode === RECORD_TYPE_BINDING_PIPE) {
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import {List} from 'angular2/src/facade/collection';
|
||||
import {Locals} from './parser/locals';
|
||||
import {AST} from './parser/ast';
|
||||
import {DEFAULT} from './constants';
|
||||
|
||||
export class ProtoChangeDetector {
|
||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
|
||||
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMemento:List):ChangeDetector{
|
||||
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List):ChangeDetector{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export class ChangeDetection {
|
||||
createProtoChangeDetector(name:string, changeControlStrategy:string):ProtoChangeDetector{
|
||||
createProtoChangeDetector(name:string, changeControlStrategy:string=DEFAULT):ProtoChangeDetector{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +36,7 @@ export class ChangeRecord {
|
|||
}
|
||||
|
||||
export class ChangeDispatcher {
|
||||
onRecordChange(directiveMemento, records:List<ChangeRecord>) {}
|
||||
invokeMementoFor(memento:any, value) {}
|
||||
}
|
||||
|
||||
export class ChangeDetector {
|
||||
|
@ -45,7 +46,7 @@ export class ChangeDetector {
|
|||
addChild(cd:ChangeDetector) {}
|
||||
removeChild(cd:ChangeDetector) {}
|
||||
remove() {}
|
||||
hydrate(context:any, locals:Locals) {}
|
||||
hydrate(context:any, locals:Locals, directives:any) {}
|
||||
dehydrate() {}
|
||||
markPathToRootAsCheckOnce() {}
|
||||
|
||||
|
|
|
@ -67,10 +67,14 @@ export class View {
|
|||
return isPresent(this.context);
|
||||
}
|
||||
|
||||
_hydrateContext(newContext, locals) {
|
||||
_setContextAndLocals(newContext, locals) {
|
||||
this.context = newContext;
|
||||
this.locals.parent = locals;
|
||||
this.changeDetector.hydrate(this.context, this.locals);
|
||||
this.changeDetector.hydrate(this.context, this.locals, this.elementInjectors);
|
||||
}
|
||||
|
||||
_hydrateChangeDetector() {
|
||||
this.changeDetector.hydrate(this.context, this.locals, this.elementInjectors);
|
||||
}
|
||||
|
||||
_dehydrateContext() {
|
||||
|
@ -117,7 +121,8 @@ export class View {
|
|||
if (this.hydrated()) throw new BaseException('The view is already hydrated.');
|
||||
|
||||
this.render = renderComponentViewRefs[renderComponentIndex++];
|
||||
this._hydrateContext(context, locals);
|
||||
|
||||
this._setContextAndLocals(context, locals);
|
||||
|
||||
// viewContainers
|
||||
for (var i = 0; i < this.viewContainers.length; i++) {
|
||||
|
@ -173,6 +178,7 @@ export class View {
|
|||
componentChildViewIndex++;
|
||||
}
|
||||
}
|
||||
this._hydrateChangeDetector();
|
||||
this.proto.renderer.setEventDispatcher(this.render, this);
|
||||
return renderComponentIndex;
|
||||
}
|
||||
|
@ -222,22 +228,9 @@ export class View {
|
|||
this.dispatchEvent(binderIndex, eventName, locals);
|
||||
}
|
||||
|
||||
onAllChangesDone(directiveMemento:DirectiveMemento) {
|
||||
var dir = directiveMemento.directive(this.elementInjectors);
|
||||
dir.onAllChangesDone();
|
||||
}
|
||||
|
||||
onChange(directiveMemento:DirectiveMemento, changes) {
|
||||
var dir = directiveMemento.directive(this.elementInjectors);
|
||||
dir.onChange(changes);
|
||||
}
|
||||
|
||||
// dispatch to element injector or text nodes based on context
|
||||
invokeMementoFor(memento:any, currentValue:any) {
|
||||
if (memento instanceof DirectiveBindingMemento) {
|
||||
var directiveMemento:DirectiveBindingMemento = memento;
|
||||
directiveMemento.invoke(currentValue, this.elementInjectors);
|
||||
} else if (memento instanceof ElementBindingMemento) {
|
||||
if (memento instanceof ElementBindingMemento) {
|
||||
var elementMemento:ElementBindingMemento = memento;
|
||||
this.proto.renderer.setElementProperty(
|
||||
this.render, elementMemento.elementIndex, elementMemento.propertyName, currentValue
|
||||
|
@ -461,7 +454,7 @@ export class DirectiveBindingMemento {
|
|||
_elementInjectorIndex:int;
|
||||
_directiveIndex:int;
|
||||
propertyName:string;
|
||||
_setter:SetterFn;
|
||||
setter:SetterFn;
|
||||
constructor(
|
||||
elementInjectorIndex:number,
|
||||
directiveIndex:number,
|
||||
|
@ -470,13 +463,13 @@ export class DirectiveBindingMemento {
|
|||
this._elementInjectorIndex = elementInjectorIndex;
|
||||
this._directiveIndex = directiveIndex;
|
||||
this.propertyName = propertyName;
|
||||
this._setter = setter;
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
invoke(currentValue:any, elementInjectors:List<ElementInjector>) {
|
||||
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
|
||||
var directive = elementInjector.getDirectiveAtIndex(this._directiveIndex);
|
||||
this._setter(directive, currentValue);
|
||||
this.setter(directive, currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,6 +479,10 @@ class DirectiveMemento {
|
|||
callOnAllChangesDone:boolean;
|
||||
callOnChange:boolean;
|
||||
|
||||
get name() {
|
||||
return `${this._elementInjectorIndex}_${this._directiveIndex}`;
|
||||
}
|
||||
|
||||
constructor(elementInjectorIndex:number, directiveIndex:number, callOnAllChangesDone:boolean,
|
||||
callOnChange:boolean) {
|
||||
this._elementInjectorIndex = elementInjectorIndex;
|
||||
|
|
|
@ -42,9 +42,10 @@ export function main() {
|
|||
var dispatcher = new TestDispatcher();
|
||||
|
||||
var variableBindings = convertLocalsToVariableBindings(locals);
|
||||
var records = [new BindingRecord(ast(exp), memo, new FakeDirectiveMemento(memo, false))];
|
||||
|
||||
var records = [new BindingRecord(ast(exp), memo, null)];
|
||||
var cd = pcd.instantiate(dispatcher, records, variableBindings, []);
|
||||
cd.hydrate(context, locals);
|
||||
cd.hydrate(context, locals, null);
|
||||
|
||||
return {"changeDetector" : cd, "dispatcher" : dispatcher};
|
||||
}
|
||||
|
@ -55,8 +56,9 @@ export function main() {
|
|||
return res["dispatcher"].log;
|
||||
}
|
||||
|
||||
function instantiate(protoChangeDetector, dispatcher, bindings) {
|
||||
return protoChangeDetector.instantiate(dispatcher, bindings, null, []);
|
||||
function instantiate(protoChangeDetector, dispatcher, bindings, directiveMementos = null) {
|
||||
if (isBlank(directiveMementos)) directiveMementos = [];
|
||||
return protoChangeDetector.instantiate(dispatcher, bindings, null, directiveMementos);
|
||||
}
|
||||
|
||||
describe(`${name} change detection`, () => {
|
||||
|
@ -68,6 +70,7 @@ export function main() {
|
|||
|
||||
it('should do simple watching', () => {
|
||||
var person = new Person("misko");
|
||||
|
||||
var c = createChangeDetector('name', 'name', person);
|
||||
var cd = c["changeDetector"];
|
||||
var dispatcher = c["dispatcher"];
|
||||
|
@ -202,7 +205,7 @@ export function main() {
|
|||
var ast = parser.parseInterpolation("B{{a}}A", "location");
|
||||
|
||||
var cd = instantiate(pcd, dispatcher, [new BindingRecord(ast, "memo", null)]);
|
||||
cd.hydrate(new TestData("value"), null);
|
||||
cd.hydrate(new TestData("value"), null, null);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
|
@ -238,98 +241,133 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("onChange", () => {
|
||||
var dirMemento1 = new FakeDirectiveMemento(1, false, true);
|
||||
var dirMemento2 = new FakeDirectiveMemento(2, false, true);
|
||||
var dirMementoNoOnChange = new FakeDirectiveMemento(3, false, false);
|
||||
var memo1 = new FakeBindingMemento("memo1");
|
||||
var memo2 = new FakeBindingMemento("memo2");
|
||||
describe("updatingDirectives", () => {
|
||||
var dirMemento1 = new FakeDirectiveMemento(0, true, true);
|
||||
var dirMemento2 = new FakeDirectiveMemento(1, true, true);
|
||||
var dirMementoNoCallbacks = new FakeDirectiveMemento(0, false, false);
|
||||
|
||||
it("should notify the dispatcher when a group of records changes", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var updateA = new FakeBindingMemento((o, v) => o.a = v, "a");
|
||||
var updateB = new FakeBindingMemento((o, v) => o.b = v, "b");
|
||||
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("1 + 2"), memo1, dirMemento1),
|
||||
new BindingRecord(ast("10 + 20"), memo2, dirMemento1),
|
||||
new BindingRecord(ast("100 + 200"), memo1, dirMemento2)
|
||||
]);
|
||||
var directive1;
|
||||
var directive2;
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedOnChange).toEqual([{'memo1': 3, 'memo2': 30}, {'memo1': 300}]);
|
||||
beforeEach(() => {
|
||||
directive1 = new TestDirective();
|
||||
directive2 = new TestDirective();
|
||||
});
|
||||
|
||||
it("should not notify the dispatcher when callOnChange is false", () => {
|
||||
it("should happen directly, without invoking the dispatcher", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("1"), memo1, dirMemento1),
|
||||
new BindingRecord(ast("2"), memo1, dirMementoNoOnChange),
|
||||
new BindingRecord(ast("3"), memo1, dirMemento2)
|
||||
]);
|
||||
var cd = instantiate(pcd, dispatcher, [new BindingRecord(ast("42"), updateA, dirMemento1)],
|
||||
[dirMemento1]);
|
||||
|
||||
cd.hydrate(null, null, [directive1])
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedOnChange).toEqual([{'memo1': 1}, {'memo1': 3}]);
|
||||
expect(dispatcher.loggedValues).toEqual([]);
|
||||
expect(directive1.a).toEqual(42);
|
||||
});
|
||||
|
||||
describe("onChange", () => {
|
||||
it("should notify the directive when a group of records changes", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("1"), updateA, dirMemento1),
|
||||
new BindingRecord(ast("2"), updateB, dirMemento1),
|
||||
new BindingRecord(ast("3"), updateA, dirMemento2)
|
||||
], [dirMemento1, dirMemento2]);
|
||||
|
||||
cd.hydrate(null, null, [directive1, directive2])
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(directive1.changes).toEqual({'a': 1, 'b': 2});
|
||||
expect(directive2.changes).toEqual({'a': 3});
|
||||
});
|
||||
|
||||
it("should not call onChange when callOnChange is false", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("1"), updateA, dirMementoNoCallbacks)
|
||||
], [dirMementoNoCallbacks]);
|
||||
|
||||
cd.hydrate(null, null, [directive1])
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(directive1.changes).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("onAllChangesDone", () => {
|
||||
it("should notify the dispatcher about processing all the children", () => {
|
||||
var memento1 = new FakeDirectiveMemento(1, false);
|
||||
var memento2 = new FakeDirectiveMemento(2, true);
|
||||
|
||||
it("should be called after processing all the children", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var cd = pcd.instantiate(dispatcher, [], null, [memento1, memento2]);
|
||||
|
||||
cd.hydrate(null, null);
|
||||
var cd = instantiate(pcd, dispatcher, [], [dirMemento1, dirMemento2]);
|
||||
cd.hydrate(null, null, [directive1, directive2]);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedValues).toEqual([
|
||||
["onAllChangesDone", memento2]
|
||||
]);
|
||||
expect(directive1.onChangesDoneCalled).toBe(true);
|
||||
expect(directive2.onChangesDoneCalled).toBe(true);
|
||||
});
|
||||
|
||||
it("should notify in reverse order so the child is always notified before the parent", () => {
|
||||
var memento1 = new FakeDirectiveMemento(1, true);
|
||||
var memento2 = new FakeDirectiveMemento(2, true);
|
||||
|
||||
it("should not be called when onAllChangesDone is false", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var cd = pcd.instantiate(dispatcher, [], null, [memento1, memento2]);
|
||||
|
||||
cd.hydrate(null, null);
|
||||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("1"), updateA, dirMementoNoCallbacks)
|
||||
], [dirMementoNoCallbacks]);
|
||||
|
||||
cd.hydrate(null, null, [directive1])
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(dispatcher.loggedValues).toEqual([
|
||||
["onAllChangesDone", memento2],
|
||||
["onAllChangesDone", memento1]
|
||||
]);
|
||||
expect(directive1.onChangesDoneCalled).toEqual(false);
|
||||
});
|
||||
|
||||
it("should notify the dispatcher before processing shadow dom children", () => {
|
||||
var memento = new FakeDirectiveMemento(1, true);
|
||||
it("should be called in reverse order so the child is always notified before the parent", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var cd = instantiate(pcd, dispatcher, [], [dirMemento1, dirMemento2]);
|
||||
|
||||
var onChangesDoneCalls = [];
|
||||
var td1;
|
||||
td1 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td1));
|
||||
var td2;
|
||||
td2 = new TestDirective(() => ListWrapper.push(onChangesDoneCalls, td2));
|
||||
cd.hydrate(null, null, [td1, td2]);
|
||||
|
||||
cd.detectChanges();
|
||||
|
||||
expect(onChangesDoneCalls).toEqual([td2, td1]);
|
||||
});
|
||||
|
||||
it("should be called before processing shadow dom children", () => {
|
||||
var pcd = createProtoChangeDetector();
|
||||
var shadowDomChildPCD = createProtoChangeDetector();
|
||||
|
||||
var parent = pcd.instantiate(dispatcher, [], null, [memento]);
|
||||
var parent = pcd.instantiate(dispatcher, [], null, [dirMemento1]);
|
||||
|
||||
var child = shadowDomChildPCD.instantiate(dispatcher, [
|
||||
new BindingRecord(ast("1"), "a", memento)], null, []);
|
||||
new BindingRecord(ast("1"), updateA, dirMemento1)], null, [dirMemento1]);
|
||||
parent.addShadowDomChild(child);
|
||||
|
||||
parent.hydrate(null, null);
|
||||
child.hydrate(null, null);
|
||||
var directiveInShadowDOm = new TestDirective();
|
||||
var parentDirective = new TestDirective(() => {
|
||||
expect(directiveInShadowDOm.a).toBe(null);
|
||||
});
|
||||
|
||||
parent.hydrate(null, null, [parentDirective]);
|
||||
child.hydrate(null, null, [directiveInShadowDOm]);
|
||||
|
||||
parent.detectChanges();
|
||||
|
||||
// loggedValues contains everything that the dispatcher received
|
||||
// the first value is the directive memento passed into onAllChangesDone
|
||||
expect(dispatcher.loggedValues).toEqual([
|
||||
["onAllChangesDone", memento],
|
||||
1
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -343,7 +381,7 @@ export function main() {
|
|||
var cd = instantiate(pcd, dispatcher, [
|
||||
new BindingRecord(ast("a"), "a", 1)
|
||||
]);
|
||||
cd.hydrate(new TestData('value'), null);
|
||||
cd.hydrate(new TestData('value'), null, null);
|
||||
|
||||
expect(() => {
|
||||
cd.checkNoChanges();
|
||||
|
@ -448,7 +486,7 @@ export function main() {
|
|||
|
||||
expect(cd.mode).toEqual(null);
|
||||
|
||||
cd.hydrate(null, null);
|
||||
cd.hydrate(null, null, null);
|
||||
|
||||
expect(cd.mode).toEqual(CHECK_ALWAYS);
|
||||
});
|
||||
|
@ -456,7 +494,7 @@ export function main() {
|
|||
it("should set the mode to CHECK_ONCE when the push change detection is used", () => {
|
||||
var proto = createProtoChangeDetector(null, ON_PUSH);
|
||||
var cd = proto.instantiate(null, [], [], []);
|
||||
cd.hydrate(null, null);
|
||||
cd.hydrate(null, null, null);
|
||||
|
||||
expect(cd.mode).toEqual(CHECK_ONCE);
|
||||
});
|
||||
|
@ -536,13 +574,13 @@ export function main() {
|
|||
var c = createChangeDetector("memo", "name");
|
||||
var cd = c["changeDetector"];
|
||||
|
||||
cd.hydrate("some context", null);
|
||||
cd.hydrate("some context", null, null);
|
||||
expect(cd.hydrated()).toBe(true);
|
||||
|
||||
cd.dehydrate();
|
||||
expect(cd.hydrated()).toBe(false);
|
||||
|
||||
cd.hydrate("other context", null);
|
||||
cd.hydrate("other context", null, null);
|
||||
expect(cd.hydrated()).toBe(true);
|
||||
});
|
||||
|
||||
|
@ -726,10 +764,33 @@ class FakePipeRegistry extends PipeRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
class TestRecord {
|
||||
class TestDirective {
|
||||
a;
|
||||
b;
|
||||
c;
|
||||
changes;
|
||||
onChangesDoneCalled;
|
||||
onChangesDoneSpy;
|
||||
|
||||
constructor(onChangesDoneSpy = null) {
|
||||
this.onChangesDoneCalled = false;
|
||||
this.onChangesDoneSpy = onChangesDoneSpy;
|
||||
this.a = null;
|
||||
this.b = null;
|
||||
this.changes = null;
|
||||
}
|
||||
|
||||
onChange(changes) {
|
||||
var r = {};
|
||||
StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue);
|
||||
this.changes = r;
|
||||
}
|
||||
|
||||
onAllChangesDone() {
|
||||
this.onChangesDoneCalled = true;
|
||||
if(isPresent(this.onChangesDoneSpy)) {
|
||||
this.onChangesDoneSpy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Person {
|
||||
|
@ -776,21 +837,31 @@ class TestData {
|
|||
}
|
||||
|
||||
class FakeDirectiveMemento {
|
||||
value:any;
|
||||
callOnAllChangesDone:boolean;
|
||||
callOnChange:boolean;
|
||||
directiveIndex:number;
|
||||
|
||||
constructor(value, callOnAllChangesDone:boolean = false, callOnChange:boolean = false) {
|
||||
this.value = value;
|
||||
constructor(directiveIndex:number = 0, callOnAllChangesDone:boolean = false, callOnChange:boolean = false) {
|
||||
this.directiveIndex = directiveIndex;
|
||||
this.callOnAllChangesDone = callOnAllChangesDone;
|
||||
this.callOnChange = callOnChange;
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.directiveIndex;
|
||||
}
|
||||
|
||||
directive(directives) {
|
||||
return directives[this.directiveIndex];
|
||||
}
|
||||
}
|
||||
|
||||
class FakeBindingMemento {
|
||||
setter:Function;
|
||||
propertyName:string;
|
||||
|
||||
constructor(propertyName:string) {
|
||||
constructor(setter:Function, propertyName:string) {
|
||||
this.setter = setter;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
}
|
||||
|
@ -798,7 +869,6 @@ class FakeBindingMemento {
|
|||
class TestDispatcher extends ChangeDispatcher {
|
||||
log:List;
|
||||
loggedValues:List;
|
||||
loggedOnChange:List;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -808,17 +878,6 @@ class TestDispatcher extends ChangeDispatcher {
|
|||
clear() {
|
||||
this.log = ListWrapper.create();
|
||||
this.loggedValues = ListWrapper.create();
|
||||
this.loggedOnChange = ListWrapper.create();
|
||||
}
|
||||
|
||||
onChange(directiveMemento, changes) {
|
||||
var r = {};
|
||||
StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue);
|
||||
ListWrapper.push(this.loggedOnChange, r);
|
||||
}
|
||||
|
||||
onAllChangesDone(directiveMemento) {
|
||||
ListWrapper.push(this.loggedValues, ["onAllChangesDone", directiveMemento]);
|
||||
}
|
||||
|
||||
invokeMementoFor(memento, value) {
|
||||
|
|
|
@ -17,6 +17,17 @@ describe('ng2 tree benchmark', function () {
|
|||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log the ng stats (update)', function(done) {
|
||||
perfUtil.runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#ng2CreateDom'],
|
||||
id: 'ng2.tree.update',
|
||||
params: [{
|
||||
name: 'depth', value: 9, scale: 'log2'
|
||||
}]
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log the baseline stats', function(done) {
|
||||
perfUtil.runClickBenchmark({
|
||||
url: URL,
|
||||
|
@ -28,4 +39,15 @@ describe('ng2 tree benchmark', function () {
|
|||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
it('should log the baseline stats (update)', function(done) {
|
||||
perfUtil.runClickBenchmark({
|
||||
url: URL,
|
||||
buttons: ['#baselineCreateDom'],
|
||||
id: 'baseline.tree',
|
||||
params: [{
|
||||
name: 'depth', value: 9, scale: 'log2'
|
||||
}]
|
||||
}).then(done, done.fail);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -185,27 +185,29 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec
|
|||
var dispatcher = new DummyDispatcher();
|
||||
var parser = new Parser(new Lexer());
|
||||
|
||||
var parentProto = changeDetection.createProtoChangeDetector('parent', null);
|
||||
var parentProto = changeDetection.createProtoChangeDetector('parent');
|
||||
var parentCd = parentProto.instantiate(dispatcher, [], [], []);
|
||||
|
||||
var targetObj = new Obj();
|
||||
var proto = changeDetection.createProtoChangeDetector("proto", null);
|
||||
var proto = changeDetection.createProtoChangeDetector("proto");
|
||||
|
||||
var directiveMemento = new FakeDirectiveMemento("target", targetObj);
|
||||
var bindingRecords = [
|
||||
new BindingRecord(parser.parseBinding('field0', null), new FakeBindingMemento(targetObj, reflector.setter("field0")), null),
|
||||
new BindingRecord(parser.parseBinding('field1', null), new FakeBindingMemento(targetObj, reflector.setter("field1")), null),
|
||||
new BindingRecord(parser.parseBinding('field2', null), new FakeBindingMemento(targetObj, reflector.setter("field2")), null),
|
||||
new BindingRecord(parser.parseBinding('field3', null), new FakeBindingMemento(targetObj, reflector.setter("field3")), null),
|
||||
new BindingRecord(parser.parseBinding('field4', null), new FakeBindingMemento(targetObj, reflector.setter("field4")), null),
|
||||
new BindingRecord(parser.parseBinding('field5', null), new FakeBindingMemento(targetObj, reflector.setter("field5")), null),
|
||||
new BindingRecord(parser.parseBinding('field6', null), new FakeBindingMemento(targetObj, reflector.setter("field6")), null),
|
||||
new BindingRecord(parser.parseBinding('field7', null), new FakeBindingMemento(targetObj, reflector.setter("field7")), null),
|
||||
new BindingRecord(parser.parseBinding('field8', null), new FakeBindingMemento(targetObj, reflector.setter("field8")), null),
|
||||
new BindingRecord(parser.parseBinding('field9', null), new FakeBindingMemento(targetObj, reflector.setter("field9")), null)
|
||||
new BindingRecord(parser.parseBinding('field0', null), new FakeBindingMemento(reflector.setter("field0"), "field0"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field1', null), new FakeBindingMemento(reflector.setter("field1"), "field1"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field2', null), new FakeBindingMemento(reflector.setter("field2"), "field2"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field3', null), new FakeBindingMemento(reflector.setter("field3"), "field3"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field4', null), new FakeBindingMemento(reflector.setter("field4"), "field4"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field5', null), new FakeBindingMemento(reflector.setter("field5"), "field5"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field6', null), new FakeBindingMemento(reflector.setter("field6"), "field6"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field7', null), new FakeBindingMemento(reflector.setter("field7"), "field7"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field8', null), new FakeBindingMemento(reflector.setter("field8"), "field8"), directiveMemento),
|
||||
new BindingRecord(parser.parseBinding('field9', null), new FakeBindingMemento(reflector.setter("field9"), "field9"), directiveMemento)
|
||||
];
|
||||
|
||||
for (var i = 0; i < iterations; ++i) {
|
||||
var cd = proto.instantiate(dispatcher, bindingRecords, [], []);
|
||||
cd.hydrate(object, null);
|
||||
var cd = proto.instantiate(dispatcher, bindingRecords, [], [directiveMemento]);
|
||||
cd.hydrate(object, null, null);
|
||||
parentCd.addChild(cd);
|
||||
}
|
||||
return parentCd;
|
||||
|
@ -298,17 +300,34 @@ export function main () {
|
|||
|
||||
class FakeBindingMemento {
|
||||
setter:Function;
|
||||
targetObj:Obj;
|
||||
propertyName:string;
|
||||
|
||||
constructor(targetObj:Obj, setter:Function) {
|
||||
this.targetObj = targetObj;
|
||||
constructor(setter:Function, propertyName:string) {
|
||||
this.setter = setter;
|
||||
this.propertyName = propertyName;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDirectiveMemento {
|
||||
targetObj:Obj;
|
||||
name:string;
|
||||
callOnChange:boolean;
|
||||
callOnAllChangesDone:boolean;
|
||||
|
||||
constructor(name, targetObj) {
|
||||
this.targetObj = targetObj;
|
||||
this.name = name;
|
||||
this.callOnChange = false;
|
||||
this.callOnAllChangesDone = false;
|
||||
}
|
||||
|
||||
directive(dirs) {
|
||||
return this.targetObj;
|
||||
}
|
||||
}
|
||||
|
||||
class DummyDispatcher extends ChangeDispatcher {
|
||||
invokeMementoFor(bindingMemento, newValue) {
|
||||
var obj = bindingMemento.targetObj;
|
||||
bindingMemento.setter(obj, newValue);
|
||||
throw "Should not be used";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue