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 MODE_ACCESSOR = "this.mode";
var TEMP_LOCAL = "temp";
var CURRENT_PROTO = "currentProto";
function typeTemplate(type:string, cons:string, detectChanges: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 `
${localDefinitions}
${changeDefinitions}
var ${TEMP_LOCAL};
var ${CHANGE_LOCAL};
var ${CURRENT_PROTO};
var ${CHANGES_LOCAL} = null;
context = ${CONTEXT_ACCESSOR};
@ -126,19 +128,11 @@ ${records}
`;
}
function notifyTemplate(index:number):string{
return `
if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
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{
function pipeCheckTemplate(protoIndex:number, context:string, bindingPropagationConfig:string, pipe:string, pipeType:string,
oldValue:string, newValue:string, change:string, invokeMementoAndAddChange:string,
addToChanges, lastInDirective:string):string{
return `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
if (${pipe} === ${UTIL}.unitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
} else if (!${pipe}.supports(${context})) {
@ -146,25 +140,29 @@ if (${pipe} === ${UTIL}.unitialized()) {
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context}, ${bindingPropagationConfig});
}
${CHANGE_LOCAL} = ${pipe}.transform(${context});
if (! ${UTIL}.noChangeMarker(${CHANGE_LOCAL})) {
${value} = ${CHANGE_LOCAL};
${newValue} = ${pipe}.transform(${context});
if (! ${UTIL}.noChangeMarker(${newValue})) {
${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 `
${CURRENT_PROTO} = ${PROTOS_ACCESSOR}[${protoIndex}];
${assignment}
if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
${change} = true;
${addRecord}
${invokeMementoAndAddChange}
${addToChanges}
${oldValue} = ${newValue};
}
${notify}
${lastInDirective}
`;
}
@ -193,9 +191,25 @@ if (${cond}) {
`;
}
function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
return `${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL},
${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
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 {
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 {
var body = this.genBody();
var body = this.genDetectChangesBody();
return detectChangesTemplate(this.typeName, body);
}
@ -295,9 +309,9 @@ export class ChangeDetectorJITGenerator {
return callOnAllChangesDoneTemplate(this.typeName, notifications.join(";\n"));
}
genBody():string {
genDetectChangesBody():string {
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 {
@ -318,27 +332,33 @@ export class ChangeDetectorJITGenerator {
genPipeCheck(r:ProtoRecord):string {
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 newValue = this.localNames[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 addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
var invokeMemento = this.getInvokeMementoAndAddChangeTemplate(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 {
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex];
var change = this.changeNames[r.selfIndex];
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()) {
return this.ifChangedGuard(r, check);
} else {
@ -405,8 +425,22 @@ export class ChangeDetectorJITGenerator {
return JSON.stringify(value);
}
genNotify(r):string{
return r.lastInDirective ? notifyTemplate(r.selfIndex - 1) : '';
getInvokeMementoAndAddChangeTemplate(r:ProtoRecord):string {
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 {

View File

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

View File

@ -89,21 +89,31 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
detectChangesInRecords(throwOnChange:boolean) {
var protos:List<ProtoRecord> = this.protos;
var updatedRecords = null;
var changes = null;
var currentDirectiveMemento = null;
for (var i = 0; i < protos.length; ++i) {
var proto:ProtoRecord = protos[i];
var change = this._check(proto);
if (isPresent(change)) {
var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
updatedRecords = ChangeDetectionUtil.addRecord(updatedRecords, record);
if (isBlank(currentDirectiveMemento)) {
currentDirectiveMemento = proto.directiveMemento;
}
if (proto.lastInDirective && isPresent(updatedRecords)) {
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]);
var change = this._check(proto);
if (isPresent(change)) {
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
this.dispatcher.invokeMementoFor(proto.bindingMemento, change.currentValue);
this.dispatcher.onRecordChange(proto.directiveMemento, updatedRecords);
updatedRecords = null;
if (isPresent(currentDirectiveMemento) && currentDirectiveMemento.callOnChange) {
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;
return false;
}

View File

@ -230,58 +230,32 @@ export class View {
handler(eventObj, this);
}
onRecordChange(directiveMemento, records:List) {
this._invokeMementos(records);
if (directiveMemento instanceof DirectiveMemento) {
this._notifyDirectiveAboutChanges(directiveMemento, records);
}
}
onAllChangesDone(directiveMemento) {
onAllChangesDone(directiveMemento:DirectiveMemento) {
var dir = directiveMemento.directive(this.elementInjectors);
dir.onAllChangesDone();
}
_invokeMementos(records:List) {
for(var i = 0; i < records.length; ++i) {
this._invokeMementoFor(records[i]);
}
}
_notifyDirectiveAboutChanges(directiveMemento, records:List) {
onChange(directiveMemento:DirectiveMemento, changes) {
var dir = directiveMemento.directive(this.elementInjectors);
if (directiveMemento.callOnChange) {
dir.onChange(this._collectChanges(records));
}
dir.onChange(changes);
}
// dispatch to element injector or text nodes based on context
_invokeMementoFor(record:ChangeRecord) {
var memento = record.bindingMemento;
// 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(record, this.elementInjectors);
directiveMemento.invoke(currentValue, this.elementInjectors);
} else if (memento instanceof ElementBindingMemento) {
var elementMemento:ElementBindingMemento = memento;
elementMemento.invoke(record, this.bindElements);
elementMemento.invoke(currentValue, this.bindElements);
} else {
// we know it refers to _textNodes.
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;
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));
}
@ -697,17 +671,15 @@ export class ProtoView {
*/
export class ElementBindingMemento {
_elementIndex:int;
_setterName:string;
_setter:SetterFn;
constructor(elementIndex:int, setterName:string, setter:SetterFn) {
constructor(elementIndex:int, setter:SetterFn) {
this._elementIndex = elementIndex;
this._setterName = setterName;
this._setter = setter;
}
invoke(record:ChangeRecord, bindElements:List) {
invoke(currentValue:any, bindElements:List) {
var element = bindElements[this._elementIndex];
this._setter(element, record.currentValue);
this._setter(element, currentValue);
}
}
@ -716,23 +688,23 @@ export class ElementBindingMemento {
export class DirectiveBindingMemento {
_elementInjectorIndex:int;
_directiveIndex:int;
_setterName:string;
propertyName:string;
_setter:SetterFn;
constructor(
elementInjectorIndex:number,
directiveIndex:number,
setterName:string,
propertyName:string,
setter:SetterFn) {
this._elementInjectorIndex = elementInjectorIndex;
this._directiveIndex = directiveIndex;
this._setterName = setterName;
this.propertyName = propertyName;
this._setter = setter;
}
invoke(record:ChangeRecord, elementInjectors:List<ElementInjector>) {
invoke(currentValue:any, elementInjectors:List<ElementInjector>) {
var elementInjector:ElementInjector = elementInjectors[this._elementInjectorIndex];
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];
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,
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';
@ -129,21 +127,21 @@ export function main() {
it("should support literal array", () => {
var c = createChangeDetector('array', '[1,2]');
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["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues).toEqual([[[1, 2]]]);
expect(c["dispatcher"].loggedValues).toEqual([[1, 2]]);
});
it("should support literal maps", () => {
var c = createChangeDetector('map', '{z:1}');
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["changeDetector"].detectChanges();
expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
expect(c["dispatcher"].loggedValues[0]['z']).toEqual(1);
});
it("should support binary operations", () => {
@ -221,11 +219,7 @@ export function main() {
cd.detectChanges();
var changeRecord = dispatcher.changeRecords[0][0];
expect(changeRecord.bindingMemento).toEqual('name');
expect(changeRecord.change.currentValue).toEqual('bob');
expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized());
expect(dispatcher.loggedValues).toEqual(['bob']);
});
});
@ -240,59 +234,43 @@ export function main() {
cd.detectChanges();
var changeRecord = dispatcher.changeRecords[0][0];
expect(changeRecord.bindingMemento).toEqual('name');
expect(changeRecord.change.currentValue).toEqual('bob state:0');
expect(changeRecord.change.previousValue).toEqual(ChangeDetectionUtil.unitialized());
expect(dispatcher.loggedValues).toEqual(['bob state:0']);
});
});
describe("group changes", () => {
var dirMemento1 = new FakeDirectiveMemento(1);
var dirMemento2 = new FakeDirectiveMemento(2);
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");
it("should notify the dispatcher when a group of records changes", () => {
var pcd = createProtoChangeDetector();
var cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("1 + 2"), "memo", dirMemento1),
new BindingRecord(ast("10 + 20"), "memo", dirMemento1),
new BindingRecord(ast("100 + 200"), "memo", dirMemento2)
new BindingRecord(ast("1 + 2"), memo1, dirMemento1),
new BindingRecord(ast("10 + 20"), memo2, dirMemento1),
new BindingRecord(ast("100 + 200"), memo1, dirMemento2)
]);
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 cd = instantiate(pcd, dispatcher, [
new BindingRecord(ast("a()"), "a", dirMemento1),
new BindingRecord(ast("b()"), "b", dirMemento2),
new BindingRecord(ast("c()"), "c", dirMemento2)
new BindingRecord(ast("1"), memo1, dirMemento1),
new BindingRecord(ast("2"), memo1, dirMementoNoOnChange),
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();
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
expect(dispatcher.loggedValues).toEqual([
["onAllChangesDone", memento],
[1]
1
]);
});
});
@ -800,58 +778,55 @@ class TestData {
class FakeDirectiveMemento {
value:any;
callOnAllChangesDone:boolean;
callOnChange:boolean;
constructor(value, callOnAllChangesDone:boolean = false) {
constructor(value, callOnAllChangesDone:boolean = false, callOnChange:boolean = false) {
this.value = value;
this.callOnAllChangesDone = callOnAllChangesDone;
this.callOnChange = callOnChange;
}
}
class FakeBindingMemento {
propertyName:string;
constructor(propertyName:string) {
this.propertyName = propertyName;
}
}
class TestDispatcher extends ChangeDispatcher {
log:List;
loggedValues:List;
changeRecords:List;
loggedOnAllChangesDone:List;
onChange:Function;
loggedOnChange:List;
constructor() {
super();
this.log = null;
this.loggedValues = null;
this.loggedOnAllChangesDone = null;
this.onChange = (_, __) => {};
this.clear();
}
clear() {
this.log = ListWrapper.create();
this.loggedValues = ListWrapper.create();
this.loggedOnAllChangesDone = ListWrapper.create();
this.changeRecords = ListWrapper.create();
this.loggedOnChange = ListWrapper.create();
}
logValue(value) {
ListWrapper.push(this.loggedValues, value);
}
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);
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) {
ListWrapper.push(this.log, `${memento}=${this._asString(value)}`);
ListWrapper.push(this.loggedValues, value);
}
_asString(value) {
return (isBlank(value) ? 'null' : value.toString());
}
}
}

View File

@ -7,11 +7,11 @@ import {Component, Decorator, Viewport, Directive, onChange, onAllChangesDone} f
import {Lexer, Parser, DynamicProtoChangeDetector,
ChangeDetector} from 'angular2/change_detection';
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 {int, IMPLEMENTS} from 'angular2/src/facade/lang';
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 {VmTurnZone} from 'angular2/src/core/zone/vm_turn_zone';
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
@ -624,14 +624,13 @@ export function main() {
ctx.b = 0;
cd.detectChanges();
expect(directive.changes).toEqual({
"a" : PropertyUpdate.createWithoutPrevious(0),
"b" : PropertyUpdate.createWithoutPrevious(0)
});
expect(directive.changes["a"].currentValue).toEqual(0);
expect(directive.changes["b"].currentValue).toEqual(0);
ctx.a = 100;
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', () => {

View File

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