refactor(change_detection): call onChange from the change detector

This commit is contained in:
vsavkin 2015-03-31 09:07:01 -07:00
parent bcbed2812d
commit abea92af59
7 changed files with 184 additions and 256 deletions

View File

@ -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 TEMP_LOCAL = "temp"; var TEMP_LOCAL = "temp";
var CURRENT_PROTO = "currentProto";
function typeTemplate(type:string, cons:string, detectChanges:string, function typeTemplate(type:string, cons:string, detectChanges:string,
notifyOnAllChangesDone:string, setContext:string):string { notifyOnAllChangesDone:string, setContext:string):string {
@ -113,12 +114,13 @@ function onAllChangesDoneTemplate(index:number):string {
} }
function bodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string { function detectChangesBodyTemplate(localDefinitions:string, changeDefinitions:string, records:string):string {
return ` return `
${localDefinitions} ${localDefinitions}
${changeDefinitions} ${changeDefinitions}
var ${TEMP_LOCAL}; var ${TEMP_LOCAL};
var ${CHANGE_LOCAL}; var ${CHANGE_LOCAL};
var ${CURRENT_PROTO};
var ${CHANGES_LOCAL} = null; var ${CHANGES_LOCAL} = null;
context = ${CONTEXT_ACCESSOR}; context = ${CONTEXT_ACCESSOR};
@ -126,19 +128,11 @@ ${records}
`; `;
} }
function notifyTemplate(index:number):string{ function pipeCheckTemplate(protoIndex:number, context:string, bindingPropagationConfig:string, pipe:string, pipeType:string,
return ` oldValue:string, newValue:string, change:string, invokeMementoAndAddChange:string,
if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) { addToChanges, lastInDirective:string):string{
if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]);
${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].directiveMemento, ${CHANGES_LOCAL});
${CHANGES_LOCAL} = null;
}
`;
}
function pipeCheckTemplate(context:string, bindingPropagationConfig:string, pipe:string, pipeType:string,
value:string, change:string, addRecord:string, notify:string):string{
return ` return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
if (${pipe} === ${UTIL}.unitialized()) { if (${pipe} === ${UTIL}.unitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig}); ${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
} else if (!${pipe}.supports(${context})) { } else if (!${pipe}.supports(${context})) {
@ -146,25 +140,29 @@ if (${pipe} === ${UTIL}.unitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig}); ${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
} }
${CHANGE_LOCAL} = ${pipe}.transform(${context}); ${newValue} = ${pipe}.transform(${context});
if (! ${UTIL}.noChangeMarker(${CHANGE_LOCAL})) { if (! ${UTIL}.noChangeMarker(${newValue})) {
${value} = ${CHANGE_LOCAL};
${change} = true; ${change} = true;
${addRecord} ${invokeMementoAndAddChange}
${addToChanges}
${oldValue} = ${newValue};
} }
${notify} ${lastInDirective}
`; `;
} }
function referenceCheckTemplate(assignment, newValue, oldValue, change, addRecord, notify) { function referenceCheckTemplate(protoIndex:number, assignment:string, oldValue:string, newValue:string, change:string,
invokeMementoAndAddChange:string, addToChanges:string, lastInDirective:string):string {
return ` return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${assignment} ${assignment}
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) { if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
${change} = true; ${change} = true;
${addRecord} ${invokeMementoAndAddChange}
${addToChanges}
${oldValue} = ${newValue}; ${oldValue} = ${newValue};
} }
${notify} ${lastInDirective}
`; `;
} }
@ -193,9 +191,25 @@ if (${cond}) {
`; `;
} }
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) { function addToChangesTemplate(oldValue:string, newValue:string):string {
return `${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL}, return `${CHANGES_LOCAL} = ${UTIL}.addChange(${CHANGES_LOCAL}, ${CURRENT_PROTO}.bindingMemento, ${UTIL}.simpleChange(${oldValue}, ${newValue}));`;
${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`; }
function invokeBindingMemento(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{
return `
if (${CHANGES_LOCAL}) {
${DISPATCHER_ACCESSOR}.onChange(${PROTOS_ACCESSOR}[${protoIndex}].directiveMemento, ${CHANGES_LOCAL});
}
${CHANGES_LOCAL} = null;
`;
} }
@ -277,7 +291,7 @@ export class ChangeDetectorJITGenerator {
} }
genDetectChanges():string { genDetectChanges():string {
var body = this.genBody(); var body = this.genDetectChangesBody();
return detectChangesTemplate(this.typeName, body); return detectChangesTemplate(this.typeName, body);
} }
@ -295,9 +309,9 @@ export class ChangeDetectorJITGenerator {
return callOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n")); return callOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n"));
} }
genBody():string { genDetectChangesBody():string {
var rec = this.records.map((r) => this.genRecord(r)).join("\n"); var rec = this.records.map((r) => this.genRecord(r)).join("\n");
return bodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec); return detectChangesBodyTemplate(this.genLocalDefinitions(), this.genChangeDefinitions(), rec);
} }
genLocalDefinitions():string { genLocalDefinitions():string {
@ -318,27 +332,33 @@ export class ChangeDetectorJITGenerator {
genPipeCheck(r:ProtoRecord):string { genPipeCheck(r:ProtoRecord):string {
var context = this.localNames[r.contextIndex]; var context = this.localNames[r.contextIndex];
var pipe = this.pipeNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this.fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex];
var change = this.changeNames[r.selfIndex]; var change = this.changeNames[r.selfIndex];
var pipe = this.pipeNames[r.selfIndex];
var bpc = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.bindingPropagationConfig" : "null"; var bpc = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.bindingPropagationConfig" : "null";
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue); var invokeMemento = this.getInvokeMementoAndAddChangeTemplate(r);
var notify = this.genNotify(r); var addToChanges = this.genAddToChanges(r);
var lastInDirective = this.genLastInDirective(r);
return pipeCheckTemplate(context, bpc, pipe, r.name, newValue, change, addRecord, notify); return pipeCheckTemplate(r.selfIndex - 1, context, bpc, pipe, r.name, oldValue, newValue, change,
invokeMemento, addToChanges, lastInDirective);
} }
genReferenceCheck(r:ProtoRecord):string { genReferenceCheck(r:ProtoRecord):string {
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex]; var oldValue = this.fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex];
var change = this.changeNames[r.selfIndex]; var change = this.changeNames[r.selfIndex];
var assignment = this.genUpdateCurrentValue(r); var assignment = this.genUpdateCurrentValue(r);
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
var check = referenceCheckTemplate(assignment, newValue, oldValue, change, r.lastInBinding ? addRecord : '', notify); var invokeMemento = this.getInvokeMementoAndAddChangeTemplate(r);
var addToChanges = this.genAddToChanges(r);
var lastInDirective = this.genLastInDirective(r);
var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change,
invokeMemento, addToChanges, lastInDirective);
if (r.isPureFunction()) { if (r.isPureFunction()) {
return this.ifChangedGuard(r, check); return this.ifChangedGuard(r, check);
} else { } else {
@ -405,8 +425,22 @@ export class ChangeDetectorJITGenerator {
return JSON.stringify(value); return JSON.stringify(value);
} }
genNotify(r):string{ getInvokeMementoAndAddChangeTemplate(r:ProtoRecord):string {
return r.lastInDirective ? notifyTemplate(r.selfIndex - 1) : ''; var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
return r.lastInBinding ? invokeBindingMemento(oldValue, newValue) : "";
}
genAddToChanges(r:ProtoRecord):string {
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
var callOnChange = r.directiveMemento && r.directiveMemento.callOnChange;
return callOnChange ? addToChangesTemplate(oldValue, newValue) : "";
}
genLastInDirective(r:ProtoRecord):string{
var callOnChange = r.directiveMemento && r.directiveMemento.callOnChange;
return r.lastInDirective && callOnChange ? lastInDirectiveTemplate(r.selfIndex - 1) : '';
} }
genArgs(r:ProtoRecord):string { genArgs(r:ProtoRecord):string {

View File

@ -3,7 +3,6 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {ProtoRecord} from './proto_record'; import {ProtoRecord} from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions'; import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {NO_CHANGE} from './pipes/pipe'; import {NO_CHANGE} from './pipes/pipe';
import {ChangeRecord, ChangeDetector} from './interfaces';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
export var uninitialized = new Object(); export var uninitialized = new Object();
@ -40,31 +39,7 @@ var _simpleChanges = [
new SimpleChange(null, null), new SimpleChange(null, null),
new SimpleChange(null, null), new SimpleChange(null, null),
new SimpleChange(null, null) new SimpleChange(null, null)
] ];
var _changeRecordsIndex = 0;
var _changeRecords = [
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null),
new ChangeRecord(null, null)
]
function _simpleChange(previousValue, currentValue) { function _simpleChange(previousValue, currentValue) {
var index = _simpleChangesIndex++ % 20; var index = _simpleChangesIndex++ % 20;
@ -74,16 +49,6 @@ function _simpleChange(previousValue, currentValue) {
return s; return s;
} }
function _changeRecord(bindingMemento, change) {
var index = _changeRecordsIndex++ % 20;
var s = _changeRecords[index];
s.bindingMemento = bindingMemento;
s.change = change;
return s;
}
var _singleElementList = [null];
export class ChangeDetectionUtil { export class ChangeDetectionUtil {
static unitialized() { static unitialized() {
return uninitialized; return uninitialized;
@ -152,33 +117,19 @@ export class ChangeDetectionUtil {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change); throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
} }
static simpleChange(previousValue:any, currentValue:any):SimpleChange {
return _simpleChange(previousValue, currentValue);
}
static changeRecord(memento:any, change:any):ChangeRecord {
return _changeRecord(memento, change);
}
static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord {
return _changeRecord(memento, _simpleChange(previousValue, currentValue));
}
static changeDetectionMode(strategy:string) { static changeDetectionMode(strategy:string) {
return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS; return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS;
} }
static addRecord(updatedRecords:List, changeRecord:ChangeRecord):List { static simpleChange(previousValue:any, currentValue:any):SimpleChange {
if (isBlank(updatedRecords)) { return _simpleChange(previousValue, currentValue);
updatedRecords = _singleElementList; }
updatedRecords[0] = changeRecord;
} else if (updatedRecords === _singleElementList) { static addChange(changes, bindingMemento, change){
updatedRecords = [_singleElementList[0], changeRecord]; if (isBlank(changes)) {
changes = {};
} else {
ListWrapper.push(updatedRecords, changeRecord);
} }
return updatedRecords; changes[bindingMemento.propertyName] = change;
return changes;
} }
} }

View File

@ -89,21 +89,31 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
detectChangesInRecords(throwOnChange:boolean) { detectChangesInRecords(throwOnChange:boolean) {
var protos:List<ProtoRecord> = this.protos; var protos:List<ProtoRecord> = this.protos;
var updatedRecords = null; var changes = null;
var currentDirectiveMemento = null;
for (var i = 0; i < protos.length; ++i) { for (var i = 0; i < protos.length; ++i) {
var proto:ProtoRecord = protos[i]; var proto:ProtoRecord = protos[i];
var change = this._check(proto); if (isBlank(currentDirectiveMemento)) {
currentDirectiveMemento = proto.directiveMemento;
if (isPresent(change)) {
var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
updatedRecords = ChangeDetectionUtil.addRecord(updatedRecords, record);
} }
if (proto.lastInDirective && isPresent(updatedRecords)) { var change = this._check(proto);
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]); if (isPresent(change)) {
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
this.dispatcher.invokeMementoFor(proto.bindingMemento, change.currentValue);
this.dispatcher.onRecordChange(proto.directiveMemento, updatedRecords); if (isPresent(currentDirectiveMemento) && currentDirectiveMemento.callOnChange) {
updatedRecords = null; changes = ChangeDetectionUtil.addChange(changes, proto.bindingMemento, change);
}
}
if (proto.lastInDirective) {
if (isPresent(changes)) {
this.dispatcher.onChange(currentDirectiveMemento, changes);
}
currentDirectiveMemento = null;
changes = null;
} }
} }
} }
@ -285,3 +295,6 @@ function isSame(a, b) {
if ((a !== a) && (b !== b)) return true; if ((a !== a) && (b !== b)) return true;
return false; return false;
} }

View File

@ -230,58 +230,32 @@ export class View {
handler(eventObj, this); handler(eventObj, this);
} }
onRecordChange(directiveMemento, records:List) { onAllChangesDone(directiveMemento:DirectiveMemento) {
this._invokeMementos(records);
if (directiveMemento instanceof DirectiveMemento) {
this._notifyDirectiveAboutChanges(directiveMemento, records);
}
}
onAllChangesDone(directiveMemento) {
var dir = directiveMemento.directive(this.elementInjectors); var dir = directiveMemento.directive(this.elementInjectors);
dir.onAllChangesDone(); dir.onAllChangesDone();
} }
_invokeMementos(records:List) { onChange(directiveMemento:DirectiveMemento, changes) {
for(var i = 0; i < records.length; ++i) {
this._invokeMementoFor(records[i]);
}
}
_notifyDirectiveAboutChanges(directiveMemento, records:List) {
var dir = directiveMemento.directive(this.elementInjectors); var dir = directiveMemento.directive(this.elementInjectors);
if (directiveMemento.callOnChange) { dir.onChange(changes);
dir.onChange(this._collectChanges(records));
}
} }
// dispatch to element injector or text nodes based on context // dispatch to element injector or text nodes based on context
_invokeMementoFor(record:ChangeRecord) { invokeMementoFor(memento:any, currentValue:any) {
var memento = record.bindingMemento;
if (memento instanceof DirectiveBindingMemento) { if (memento instanceof DirectiveBindingMemento) {
var directiveMemento:DirectiveBindingMemento = memento; var directiveMemento:DirectiveBindingMemento = memento;
directiveMemento.invoke(record, this.elementInjectors); directiveMemento.invoke(currentValue, this.elementInjectors);
} else if (memento instanceof ElementBindingMemento) { } else if (memento instanceof ElementBindingMemento) {
var elementMemento:ElementBindingMemento = memento; var elementMemento:ElementBindingMemento = memento;
elementMemento.invoke(record, this.bindElements); elementMemento.invoke(currentValue, this.bindElements);
} else { } else {
// we know it refers to _textNodes. // we know it refers to _textNodes.
var textNodeIndex:number = memento; var textNodeIndex:number = memento;
DOM.setText(this.textNodes[textNodeIndex], record.currentValue); DOM.setText(this.textNodes[textNodeIndex], currentValue);
} }
} }
_collectChanges(records:List) {
var changes = StringMapWrapper.create();
for(var i = 0; i < records.length; ++i) {
var record = records[i];
var propertyUpdate = new PropertyUpdate(record.currentValue, record.previousValue);
StringMapWrapper.set(changes, record.bindingMemento._setterName, propertyUpdate);
}
return changes;
}
} }
/** /**
@ -604,7 +578,7 @@ export class ProtoView {
elBinder.hasElementPropertyBindings = true; elBinder.hasElementPropertyBindings = true;
this.elementsWithBindingCount++; this.elementsWithBindingCount++;
} }
var memento = new ElementBindingMemento(this.elementsWithBindingCount-1, setterName, setter); var memento = new ElementBindingMemento(this.elementsWithBindingCount-1, setter);
ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null)); ListWrapper.push(this.bindingRecords, new BindingRecord(expression, memento, null));
} }
@ -697,17 +671,15 @@ export class ProtoView {
*/ */
export class ElementBindingMemento { export class ElementBindingMemento {
_elementIndex:int; _elementIndex:int;
_setterName:string;
_setter:SetterFn; _setter:SetterFn;
constructor(elementIndex:int, setterName:string, setter:SetterFn) { constructor(elementIndex:int, setter:SetterFn) {
this._elementIndex = elementIndex; this._elementIndex = elementIndex;
this._setterName = setterName;
this._setter = setter; this._setter = setter;
} }
invoke(record:ChangeRecord, bindElements:List) { invoke(currentValue:any, bindElements:List) {
var element = bindElements[this._elementIndex]; var element = bindElements[this._elementIndex];
this._setter(element, record.currentValue); this._setter(element, currentValue);
} }
} }
@ -716,23 +688,23 @@ export class ElementBindingMemento {
export class DirectiveBindingMemento { export class DirectiveBindingMemento {
_elementInjectorIndex:int; _elementInjectorIndex:int;
_directiveIndex:int; _directiveIndex:int;
_setterName:string; propertyName:string;
_setter:SetterFn; _setter:SetterFn;
constructor( constructor(
elementInjectorIndex:number, elementInjectorIndex:number,
directiveIndex:number, directiveIndex:number,
setterName:string, propertyName:string,
setter:SetterFn) { setter:SetterFn) {
this._elementInjectorIndex = elementInjectorIndex; this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex; this._directiveIndex = directiveIndex;
this._setterName = setterName; this.propertyName = propertyName;
this._setter = setter; this._setter = setter;
} }
invoke(record:ChangeRecord, elementInjectors:List<ElementInjector>) { invoke(currentValue:any, elementInjectors:List<ElementInjector>) {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex]; var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
var directive = elementInjector.getDirectiveAtIndex(this._directiveIndex); var directive = elementInjector.getDirectiveAtIndex(this._directiveIndex);
this._setter(directive, record.currentValue); this._setter(directive, currentValue);
} }
} }
@ -754,20 +726,4 @@ class DirectiveMemento {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex]; var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
return elementInjector.getDirectiveAtIndex(this._directiveIndex); return elementInjector.getDirectiveAtIndex(this._directiveIndex);
} }
} }
/**
*/
export class PropertyUpdate {
currentValue;
previousValue;
constructor(currentValue, previousValue) {
this.currentValue = currentValue;
this.previousValue = previousValue;
}
static createWithoutPrevious(currentValue) {
return new PropertyUpdate(currentValue, uninitialized);
}
}

View File

@ -10,8 +10,6 @@ import {Locals} from 'angular2/src/change_detection/parser/locals';
import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord, import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, BindingRecord,
PipeRegistry, Pipe, NO_CHANGE, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT} from 'angular2/change_detection'; PipeRegistry, Pipe, NO_CHANGE, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH, DEFAULT} from 'angular2/change_detection';
import {ChangeDetectionUtil} from 'angular2/src/change_detection/change_detection_util';
import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/change_detection/proto_change_detector'; import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/change_detection/proto_change_detector';
@ -129,21 +127,21 @@ export function main() {
it("should support literal array", () => { it("should support literal array", () => {
var c = createChangeDetector('array', '[1,2]'); var c = createChangeDetector('array', '[1,2]');
c["changeDetector"].detectChanges(); c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]); expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]);
c = createChangeDetector('array', '[1,a]', new TestData(2)); c = createChangeDetector('array', '[1,a]', new TestData(2));
c["changeDetector"].detectChanges(); c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]); expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]);
}); });
it("should support literal maps", () => { it("should support literal maps", () => {
var c = createChangeDetector('map', '{z:1}'); var c = createChangeDetector('map', '{z:1}');
c["changeDetector"].detectChanges(); c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1); expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1);
c = createChangeDetector('map', '{z:a}', new TestData(1)); c = createChangeDetector('map', '{z:a}', new TestData(1));
c["changeDetector"].detectChanges(); c["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1); expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1);
}); });
it("should support binary operations", () => { it("should support binary operations", () => {
@ -221,11 +219,7 @@ export function main() {
cd.detectChanges(); cd.detectChanges();
var changeRecord = dispatcher.changeRecords[0][0]; expect(dispatcher.loggedValues).toEqual(['bob']);
expect(changeRecord.bindingMemento).toEqual('name');
expect(changeRecord.change.currentValue).toEqual('bob');
expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized());
}); });
}); });
@ -240,59 +234,43 @@ export function main() {
cd.detectChanges(); cd.detectChanges();
var changeRecord = dispatcher.changeRecords[0][0]; expect(dispatcher.loggedValues).toEqual(['bob state:0']);
expect(changeRecord.bindingMemento).toEqual('name');
expect(changeRecord.change.currentValue).toEqual('bob state:0');
expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized());
}); });
}); });
describe("group changes", () => { describe("onChange", () => {
var dirMemento1 = new FakeDirectiveMemento(1); var dirMemento1 = new FakeDirectiveMemento(1, false, true);
var dirMemento2 = new FakeDirectiveMemento(2); var dirMemento2 = new FakeDirectiveMemento(2, false, true);
var dirMementoNoOnChange = new FakeDirectiveMemento(3, false, false);
var memo1 = new FakeBindingMemento("memo1");
var memo2 = new FakeBindingMemento("memo2");
it("should notify the dispatcher when a group of records changes", () => { it("should notify the dispatcher when a group of records changes", () => {
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
var cd = instantiate(pcd, dispatcher, [ var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("1 + 2"), "memo", dirMemento1), new BindingRecord(ast("1 + 2"), memo1, dirMemento1),
new BindingRecord(ast("10 + 20"), "memo", dirMemento1), new BindingRecord(ast("10 + 20"), memo2, dirMemento1),
new BindingRecord(ast("100 + 200"), "memo", dirMemento2) new BindingRecord(ast("100 + 200"), memo1, dirMemento2)
]); ]);
cd.detectChanges(); cd.detectChanges();
expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]); expect(dispatcher.loggedOnChange).toEqual([{'memo1': 3, 'memo2': 30}, {'memo1': 300}]);
}); });
it("should notify the dispatcher before switching to the next group", () => { it("should not notify the dispatcher when callOnChange is false", () => {
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
var cd = instantiate(pcd, dispatcher, [ var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("a()"), "a", dirMemento1), new BindingRecord(ast("1"), memo1, dirMemento1),
new BindingRecord(ast("b()"), "b", dirMemento2), new BindingRecord(ast("2"), memo1, dirMementoNoOnChange),
new BindingRecord(ast("c()"), "c", dirMemento2) new BindingRecord(ast("3"), memo1, dirMemento2)
]); ]);
var tr = new TestRecord();
tr.a = () => {
dispatcher.logValue('InvokeA');
return 'a'
};
tr.b = () => {
dispatcher.logValue('InvokeB');
return 'b'
};
tr.c = () => {
dispatcher.logValue('InvokeC');
return 'c'
};
cd.hydrate(tr, null);
cd.detectChanges(); cd.detectChanges();
expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]); expect(dispatcher.loggedOnChange).toEqual([{'memo1': 1}, {'memo1': 3}]);
}); });
}); });
@ -350,7 +328,7 @@ export function main() {
// the first value is the directive memento passed into onAllChangesDone // the first value is the directive memento passed into onAllChangesDone
expect(dispatcher.loggedValues).toEqual([ expect(dispatcher.loggedValues).toEqual([
["onAllChangesDone", memento], ["onAllChangesDone", memento],
[1] 1
]); ]);
}); });
}); });
@ -800,58 +778,55 @@ class TestData {
class FakeDirectiveMemento { class FakeDirectiveMemento {
value:any; value:any;
callOnAllChangesDone:boolean; callOnAllChangesDone:boolean;
callOnChange:boolean;
constructor(value, callOnAllChangesDone:boolean = false) { constructor(value, callOnAllChangesDone:boolean = false, callOnChange:boolean = false) {
this.value = value; this.value = value;
this.callOnAllChangesDone = callOnAllChangesDone; this.callOnAllChangesDone = callOnAllChangesDone;
this.callOnChange = callOnChange;
}
}
class FakeBindingMemento {
propertyName:string;
constructor(propertyName:string) {
this.propertyName = propertyName;
} }
} }
class TestDispatcher extends ChangeDispatcher { class TestDispatcher extends ChangeDispatcher {
log:List; log:List;
loggedValues:List; loggedValues:List;
changeRecords:List; loggedOnChange:List;
loggedOnAllChangesDone:List;
onChange:Function;
constructor() { constructor() {
super(); super();
this.log = null;
this.loggedValues = null;
this.loggedOnAllChangesDone = null;
this.onChange = (_, __) => {};
this.clear(); this.clear();
} }
clear() { clear() {
this.log = ListWrapper.create(); this.log = ListWrapper.create();
this.loggedValues = ListWrapper.create(); this.loggedValues = ListWrapper.create();
this.loggedOnAllChangesDone = ListWrapper.create(); this.loggedOnChange = ListWrapper.create();
this.changeRecords = ListWrapper.create();
} }
logValue(value) { onChange(directiveMemento, changes) {
ListWrapper.push(this.loggedValues, value); var r = {};
} StringMapWrapper.forEach(changes, (c, key) => r[key] = c.currentValue);
ListWrapper.push(this.loggedOnChange, r);
onRecordChange(directiveMemento, changeRecords:List) {
var value = changeRecords[0].change.currentValue;
var memento = changeRecords[0].bindingMemento;
ListWrapper.push(this.log, memento + '=' + this._asString(value));
var values = ListWrapper.map(changeRecords, (r) => r.change.currentValue);
ListWrapper.push(this.loggedValues, values);
ListWrapper.push(this.changeRecords, changeRecords);
this.onChange(directiveMemento, changeRecords);
} }
onAllChangesDone(directiveMemento) { onAllChangesDone(directiveMemento) {
ListWrapper.push(this.loggedValues, ["onAllChangesDone", directiveMemento]); ListWrapper.push(this.loggedValues, ["onAllChangesDone", directiveMemento]);
} }
invokeMementoFor(memento, value) {
ListWrapper.push(this.log, `${memento}=${this._asString(value)}`);
ListWrapper.push(this.loggedValues, value);
}
_asString(value) { _asString(value) {
return (isBlank(value) ? 'null' : value.toString()); return (isBlank(value) ? 'null' : value.toString());
} }
} }

View File

@ -7,11 +7,11 @@ import {Component, Decorator, Viewport, Directive, onChange, onAllChangesDone} f
import {Lexer, Parser, DynamicProtoChangeDetector, import {Lexer, Parser, DynamicProtoChangeDetector,
ChangeDetector} from 'angular2/change_detection'; ChangeDetector} from 'angular2/change_detection';
import {EventEmitter} from 'angular2/src/core/annotations/di'; import {EventEmitter} from 'angular2/src/core/annotations/di';
import {List, MapWrapper} from 'angular2/src/facade/collection'; import {List, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {DOM} from 'angular2/src/dom/dom_adapter'; import {DOM} from 'angular2/src/dom/dom_adapter';
import {int, IMPLEMENTS} from 'angular2/src/facade/lang'; import {int, IMPLEMENTS} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di'; import {Injector} from 'angular2/di';
import {View, PropertyUpdate} from 'angular2/src/core/compiler/view'; import {View} from 'angular2/src/core/compiler/view';
import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone'; import {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager'; import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
@ -624,14 +624,13 @@ export function main() {
ctx.b = 0; ctx.b = 0;
cd.detectChanges(); cd.detectChanges();
expect(directive.changes).toEqual({ expect(directive.changes["a"].currentValue).toEqual(0);
"a" : PropertyUpdate.createWithoutPrevious(0), expect(directive.changes["b"].currentValue).toEqual(0);
"b" : PropertyUpdate.createWithoutPrevious(0)
});
ctx.a = 100; ctx.a = 100;
cd.detectChanges(); cd.detectChanges();
expect(directive.changes).toEqual({"a" : new PropertyUpdate(100, 0)}); expect(directive.changes["a"].currentValue).toEqual(100);
expect(StringMapWrapper.contains(directive.changes, "b")).toBe(false);
}); });
it('should invoke the onAllChangesDone callback', () => { it('should invoke the onAllChangesDone callback', () => {

View File

@ -109,16 +109,16 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) {
var proto = changeDetection.createProtoChangeDetector("proto"); var proto = changeDetection.createProtoChangeDetector("proto");
var bindingRecords = [ var bindingRecords = [
new BindingRecord(parser.parseBinding('field0', null), "memo", 0), new BindingRecord(parser.parseBinding('field0', null), "memo", null),
new BindingRecord(parser.parseBinding('field1', null), "memo", 1), new BindingRecord(parser.parseBinding('field1', null), "memo", null),
new BindingRecord(parser.parseBinding('field2', null), "memo", 2), new BindingRecord(parser.parseBinding('field2', null), "memo", null),
new BindingRecord(parser.parseBinding('field3', null), "memo", 3), new BindingRecord(parser.parseBinding('field3', null), "memo", null),
new BindingRecord(parser.parseBinding('field4', null), "memo", 4), new BindingRecord(parser.parseBinding('field4', null), "memo", null),
new BindingRecord(parser.parseBinding('field5', null), "memo", 5), new BindingRecord(parser.parseBinding('field5', null), "memo", null),
new BindingRecord(parser.parseBinding('field6', null), "memo", 6), new BindingRecord(parser.parseBinding('field6', null), "memo", null),
new BindingRecord(parser.parseBinding('field7', null), "memo", 7), new BindingRecord(parser.parseBinding('field7', null), "memo", null),
new BindingRecord(parser.parseBinding('field8', null), "memo", 8), new BindingRecord(parser.parseBinding('field8', null), "memo", null),
new BindingRecord(parser.parseBinding('field9', null), "memo", 9) new BindingRecord(parser.parseBinding('field9', null), "memo", null)
]; ];
for (var i = 0; i < iterations; ++i) { for (var i = 0; i < iterations; ++i) {
@ -215,6 +215,6 @@ export function main () {
class DummyDispatcher extends ChangeDispatcher { class DummyDispatcher extends ChangeDispatcher {
onRecordChange(record, context) { invokeMementoFor(binding, newValue) {
} }
} }