diff --git a/modules/benchmarks/src/change_detection/change_detection_benchmark.js b/modules/benchmarks/src/change_detection/change_detection_benchmark.js
index 724591e4d8..cb5bbe1f4b 100644
--- a/modules/benchmarks/src/change_detection/change_detection_benchmark.js
+++ b/modules/benchmarks/src/change_detection/change_detection_benchmark.js
@@ -8,6 +8,7 @@ import {
Parser,
ChangeDetector,
ProtoChangeDetector,
+ DynamicProtoChangeDetector,
ChangeDispatcher,
} from 'change_detection/change_detection';
@@ -102,7 +103,7 @@ function setUpChangeDetection(iterations) {
var dispatcher = new DummyDispatcher();
var parser = new Parser(new Lexer());
- var parentProto = new ProtoChangeDetector();
+ var parentProto = new DynamicProtoChangeDetector();
var parentCD = parentProto.instantiate(dispatcher, MapWrapper.create());
var astWithSource = [
@@ -119,7 +120,7 @@ function setUpChangeDetection(iterations) {
];
function proto(i) {
- var pcd = new ProtoChangeDetector();
+ var pcd = new DynamicProtoChangeDetector();
pcd.addAst(astWithSource[i % 10].ast, "memo", i, false);
return pcd;
}
diff --git a/modules/benchmarks/src/compiler/compiler_benchmark.js b/modules/benchmarks/src/compiler/compiler_benchmark.js
index 5772cacf4d..b71c37fdf9 100644
--- a/modules/benchmarks/src/compiler/compiler_benchmark.js
+++ b/modules/benchmarks/src/compiler/compiler_benchmark.js
@@ -3,7 +3,7 @@ import {isBlank, Type} from 'facade/lang';
import {MapWrapper} from 'facade/collection';
import {DirectiveMetadata} from 'core/compiler/directive_metadata';
-import {Parser, Lexer, ProtoRecordRange} from 'change_detection/change_detection';
+import {Parser, Lexer, ProtoRecordRange, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
@@ -79,7 +79,7 @@ export function main() {
setupReflector();
var reader = new DirectiveMetadataReader();
var cache = new CompilerCache();
- var compiler = new Compiler(null, reader, new Parser(new Lexer()), cache);
+ var compiler = new Compiler(dynamicChangeDetection, null, reader, new Parser(new Lexer()), cache);
var annotatedComponent = reader.read(BenchmarkComponent);
var templateNoBindings = loadTemplate('templateNoBindings', count);
diff --git a/modules/benchmarks/src/tree/tree_benchmark.js b/modules/benchmarks/src/tree/tree_benchmark.js
index 0aae941fba..f9b4116689 100644
--- a/modules/benchmarks/src/tree/tree_benchmark.js
+++ b/modules/benchmarks/src/tree/tree_benchmark.js
@@ -1,4 +1,5 @@
-import {Parser, Lexer, ChangeDetector} from 'change_detection/change_detection';
+import {Parser, Lexer, ChangeDetector, ChangeDetection, jitChangeDetection}
+ from 'change_detection/change_detection';
import {bootstrap, Component, Template, TemplateConfig, ViewPort, Compiler} from 'angular/angular';
@@ -38,11 +39,7 @@ function setupReflector() {
},
template: new TemplateConfig({
directives: [TreeComponent, NgIf],
- inline: `
- {{data.value}}
-
-
- `
+ inline: `{{data.value}}`
})
})]
});
@@ -59,8 +56,8 @@ function setupReflector() {
});
reflector.registerType(Compiler, {
- 'factory': (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache),
- 'parameters': [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
+ 'factory': (cd, templateLoader, reader, parser, compilerCache) => new Compiler(cd, templateLoader, reader, parser, compilerCache),
+ 'parameters': [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
'annotations': []
});
diff --git a/modules/change_detection/src/abstract_change_detector.js b/modules/change_detection/src/abstract_change_detector.js
new file mode 100644
index 0000000000..2a2b4829c4
--- /dev/null
+++ b/modules/change_detection/src/abstract_change_detector.js
@@ -0,0 +1,46 @@
+import {List, ListWrapper} from 'facade/collection';
+import {ChangeDetector} from './interfaces';
+
+export class AbstractChangeDetector extends ChangeDetector {
+ children:List;
+ parent:ChangeDetector;
+
+ constructor() {
+ this.children = [];
+ }
+
+ addChild(cd:ChangeDetector) {
+ ListWrapper.push(this.children, cd);
+ cd.parent = this;
+ }
+
+ removeChild(cd:ChangeDetector) {
+ ListWrapper.remove(this.children, cd);
+ }
+
+ remove() {
+ this.parent.removeChild(this);
+ }
+
+ detectChanges() {
+ this._detectChanges(false);
+ }
+
+ checkNoChanges() {
+ this._detectChanges(true);
+ }
+
+ _detectChanges(throwOnChange:boolean) {
+ this.detectChangesInRecords(throwOnChange);
+ this._detectChangesInChildren(throwOnChange);
+ }
+
+ detectChangesInRecords(throwOnChange:boolean){}
+
+ _detectChangesInChildren(throwOnChange:boolean) {
+ var children = this.children;
+ for(var i = 0; i < children.length; ++i) {
+ children[i]._detectChanges(throwOnChange);
+ }
+ }
+}
diff --git a/modules/change_detection/src/change_detection.js b/modules/change_detection/src/change_detection.js
index 32fac81672..1e53b26351 100644
--- a/modules/change_detection/src/change_detection.js
+++ b/modules/change_detection/src/change_detection.js
@@ -5,5 +5,26 @@ export {ContextWithVariableBindings} from './parser/context_with_variable_bindin
export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
export {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
-export {ProtoChangeDetector} from './proto_change_detector';
-export {DynamicChangeDetector} from './dynamic_change_detector';
\ No newline at end of file
+export {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} from './proto_change_detector';
+export {DynamicChangeDetector} from './dynamic_change_detector';
+
+import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} from './proto_change_detector';
+
+export class ChangeDetection {
+ createProtoChangeDetector(name:string){}
+}
+
+export class DynamicChangeDetection extends ChangeDetection {
+ createProtoChangeDetector(name:string):ProtoChangeDetector{
+ return new DynamicProtoChangeDetector();
+ }
+}
+
+export class JitChangeDetection extends ChangeDetection {
+ createProtoChangeDetector(name:string):ProtoChangeDetector{
+ return new JitProtoChangeDetector();
+ }
+}
+
+export var dynamicChangeDetection = new DynamicChangeDetection();
+export var jitChangeDetection = new JitChangeDetection();
\ No newline at end of file
diff --git a/modules/change_detection/src/change_detection_jit_generator.dart b/modules/change_detection/src/change_detection_jit_generator.dart
new file mode 100644
index 0000000000..32905f20c9
--- /dev/null
+++ b/modules/change_detection/src/change_detection_jit_generator.dart
@@ -0,0 +1,10 @@
+library change_detectoin.change_detection_jit_generator;
+
+class ChangeDetectorJITGenerator {
+ ChangeDetectorJITGenerator(typeName, records) {
+ }
+
+ generate() {
+ throw "Not supported in Dart";
+ }
+}
\ No newline at end of file
diff --git a/modules/change_detection/src/change_detection_jit_generator.es6 b/modules/change_detection/src/change_detection_jit_generator.es6
new file mode 100644
index 0000000000..074dbac96a
--- /dev/null
+++ b/modules/change_detection/src/change_detection_jit_generator.es6
@@ -0,0 +1,362 @@
+import {isPresent, isBlank, BaseException, Type} from 'facade/lang';
+import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
+
+import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
+import {AbstractChangeDetector} from './abstract_change_detector';
+import {ChangeDetectionUtil} from './change_detection_util';
+
+import {
+ ProtoRecord,
+ RECORD_TYPE_SELF,
+ RECORD_TYPE_PROPERTY,
+ RECORD_TYPE_INVOKE_METHOD,
+ RECORD_TYPE_CONST,
+ RECORD_TYPE_INVOKE_CLOSURE,
+ RECORD_TYPE_PRIMITIVE_OP,
+ RECORD_TYPE_KEYED_ACCESS,
+ RECORD_TYPE_INVOKE_FORMATTER,
+ RECORD_TYPE_STRUCTURAL_CHECK,
+ RECORD_TYPE_INTERPOLATE,
+ ProtoChangeDetector
+ } from './proto_change_detector';
+
+/**
+ * The code generator takes a list of proto records and creates a function/class
+ * that "emulates" what the developer would write by hand to implement the same
+ * kind of behaviour.
+ *
+ * For example: An expression `address.city` will result in the following class:
+ *
+ * var ChangeDetector0 = function ChangeDetector0(dispatcher, formatters, protos) {
+ * AbstractChangeDetector.call(this);
+ * this.dispatcher = dispatcher;
+ * this.formatters = formatters;
+ * this.protos = protos;
+ *
+ * this.context = null;
+ * this.address0 = null;
+ * this.city1 = null;
+ * }
+ * ChangeDetector0.prototype = Object.create(AbstractChangeDetector.prototype);
+ *
+ * ChangeDetector0.prototype.detectChangesInRecords = function(throwOnChange) {
+ * var address0;
+ * var city1;
+ * var change;
+ * var changes = [];
+ * var temp;
+ * var context = this.context;
+ *
+ * temp = ChangeDetectionUtil.findContext("address", context);
+ * if (temp instanceof ContextWithVariableBindings) {
+ * address0 = temp.get('address');
+ * } else {
+ * address0 = temp.address;
+ * }
+ *
+ * if (address0 !== this.address0) {
+ * this.address0 = address0;
+ * }
+ *
+ * city1 = address0.city;
+ * if (city1 !== this.city1) {
+ * changes.push(ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1));
+ * this.city1 = city1;
+ * }
+ *
+ * if (changes.length > 0) {
+ * if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]);
+ * this.dispatcher.onRecordChange('address.city', changes);
+ * changes = [];
+ * }
+ * }
+ *
+ *
+ * ChangeDetector0.prototype.setContext = function(context) {
+ * this.context = context;
+ * }
+ *
+ * return ChangeDetector0;
+ *
+ *
+ * The only thing the generated class depends on is the super class AbstractChangeDetector.
+ *
+ * The implementation comprises two parts:
+ * * ChangeDetectorJITGenerator has the logic of how everything fits together.
+ * * template functions (e.g., constructorTemplate) define what code is generated.
+*/
+
+var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
+var UTIL = "ChangeDetectionUtil";
+var DISPATCHER_ACCESSOR = "this.dispatcher";
+var FORMATTERS_ACCESSOR = "this.formatters";
+var PROTOS_ACCESSOR = "this.protos";
+var CHANGE_LOCAL = "change";
+var CHANGES_LOCAL = "changes";
+var TEMP_LOCAL = "temp";
+
+function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
+ return `
+${cons}
+${detectChanges}
+${setContext};
+
+return function(dispatcher, formatters) {
+ return new ${type}(dispatcher, formatters, protos);
+}
+`;
+}
+
+function constructorTemplate(type:string, fieldsDefinitions:string):string {
+ return `
+var ${type} = function ${type}(dispatcher, formatters, protos) {
+${ABSTRACT_CHANGE_DETECTOR}.call(this);
+${DISPATCHER_ACCESSOR} = dispatcher;
+${FORMATTERS_ACCESSOR} = formatters;
+${PROTOS_ACCESSOR} = protos;
+${fieldsDefinitions}
+}
+
+${type}.prototype = Object.create(${ABSTRACT_CHANGE_DETECTOR}.prototype);
+`;
+}
+
+function setContextTemplate(type:string):string {
+ return `
+${type}.prototype.setContext = function(context) {
+ this.context = context;
+}
+`;
+}
+
+function detectChangesTemplate(type:string, body:string):string {
+ return `
+${type}.prototype.detectChangesInRecords = function(throwOnChange) {
+ ${body}
+}
+`;
+}
+
+
+function bodyTemplate(localDefinitions:string, records:string):string {
+ return `
+${localDefinitions}
+var ${TEMP_LOCAL};
+var ${CHANGE_LOCAL};
+var ${CHANGES_LOCAL} = [];
+
+context = this.context;
+${records}
+`;
+}
+
+function notifyTemplate(index:number):string{
+ return `
+if (${CHANGES_LOCAL}.length > 0) {
+ if(throwOnChange) ${UTIL}.throwOnChange(${PROTOS_ACCESSOR}[${index}], ${CHANGES_LOCAL}[0]);
+ ${DISPATCHER_ACCESSOR}.onRecordChange(${PROTOS_ACCESSOR}[${index}].groupMemento, ${CHANGES_LOCAL});
+ ${CHANGES_LOCAL} = [];
+}
+`;
+}
+
+
+function structuralCheckTemplate(selfIndex:number, field:string, context:string, notify:string):string{
+ return `
+${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context});
+if (${CHANGE_LOCAL}) {
+ ${CHANGES_LOCAL}.push(${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL}));
+ ${field} = ${CHANGE_LOCAL}.currentValue;
+}
+${notify}
+`;
+}
+
+function referenceCheckTemplate(assignment, newValue, oldValue, addRecord, notify) {
+ return `
+${assignment}
+if (${newValue} !== ${oldValue} || (${newValue} !== ${newValue}) && (${oldValue} !== ${oldValue})) {
+ ${addRecord}
+ ${oldValue} = ${newValue};
+}
+${notify}
+`;
+}
+
+function assignmentTemplate(field:string, value:string) {
+ return `${field} = ${value};`;
+}
+
+function propertyReadTemplate(name:string, context:string, newValue:string) {
+ return `
+${TEMP_LOCAL} = ${UTIL}.findContext("${name}", ${context});
+if (${TEMP_LOCAL} instanceof ContextWithVariableBindings) {
+ ${newValue} = ${TEMP_LOCAL}.get('${name}');
+} else {
+ ${newValue} = ${TEMP_LOCAL}.${name};
+}
+`;
+}
+
+function localDefinitionsTemplate(names:List):string {
+ return names.map((n) => `var ${n};`).join("\n");
+}
+
+function fieldDefinitionsTemplate(names:List):string {
+ return names.map((n) => `${n} = ${UTIL}.unitialized();`).join("\n");
+}
+
+function addSimpleChangeRecordTemplate(protoIndex:number, oldValue:string, newValue:string) {
+ return `${CHANGES_LOCAL}.push(${UTIL}.simpleChangeRecord(${PROTOS_ACCESSOR}[${protoIndex}].bindingMemento, ${oldValue}, ${newValue}));`;
+}
+
+
+export class ChangeDetectorJITGenerator {
+ typeName:string;
+ records:List;
+ localNames:List;
+ fieldNames:List;
+
+ constructor(typeName:string, records:List) {
+ this.typeName = typeName;
+ this.records = records;
+
+ this.localNames = this.getLocalNames(records);
+ this.fieldNames = this.getFieldNames(this.localNames);
+ }
+
+ getLocalNames(records:List):List {
+ var index = 0;
+ var names = records.map((r) => {
+ var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
+ return `${sanitizedName}${index++}`
+ });
+ return ["context"].concat(names);
+ }
+
+ getFieldNames(localNames:List):List {
+ return localNames.map((n) => `this.${n}`);
+ }
+
+
+ generate():Function {
+ var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext());
+ return new Function('AbstractChangeDetector', 'ChangeDetectionUtil', 'ContextWithVariableBindings', 'protos', text)(AbstractChangeDetector, ChangeDetectionUtil, ContextWithVariableBindings, this.records);
+ }
+
+ genConstructor():string {
+ return constructorTemplate(this.typeName, fieldDefinitionsTemplate(this.fieldNames));
+ }
+
+ genSetContext():string {
+ return setContextTemplate(this.typeName);
+ }
+
+ genDetectChanges():string {
+ var body = this.genBody();
+ return detectChangesTemplate(this.typeName, body);
+ }
+
+ genBody():string {
+ var rec = this.records.map((r) => this.genRecord(r)).join("\n");
+ return bodyTemplate(this.genLocalDefinitions(), rec);
+ }
+
+ genLocalDefinitions():string {
+ return localDefinitionsTemplate(this.localNames);
+ }
+
+ genRecord(r:ProtoRecord):string {
+ if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
+ return this.getStructuralCheck(r);
+ } else {
+ return this.genReferenceCheck(r);
+ }
+ }
+
+ getStructuralCheck(r:ProtoRecord):string {
+ var field = this.fieldNames[r.selfIndex];
+ var context = this.localNames[r.contextIndex];
+ return structuralCheckTemplate(r.selfIndex - 1, field, context, this.genNotify(r));
+ }
+
+ genReferenceCheck(r:ProtoRecord):string {
+ var newValue = this.localNames[r.selfIndex];
+ var oldValue = this.fieldNames[r.selfIndex];
+ var assignment = this.genUpdateCurrentValue(r);
+ var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
+ var notify = this.genNotify(r);
+ return referenceCheckTemplate(assignment, newValue, oldValue, r.lastInBinding ? addRecord : '', notify);
+ }
+
+ genUpdateCurrentValue(r:ProtoRecord):string {
+ var context = this.localNames[r.contextIndex];
+ var newValue = this.localNames[r.selfIndex];
+ var args = this.genArgs(r);
+
+ switch (r.mode) {
+ case RECORD_TYPE_SELF:
+ throw new BaseException("Cannot evaluate self");
+
+ case RECORD_TYPE_CONST:
+ return `${newValue} = ${this.genLiteral(r.funcOrValue)}`;
+
+ case RECORD_TYPE_PROPERTY:
+ if (r.contextIndex == 0) { // only the first property read can be a local
+ return propertyReadTemplate(r.name, context, newValue);
+ } else {
+ return assignmentTemplate(newValue, `${context}.${r.name}`);
+ }
+
+ case RECORD_TYPE_INVOKE_METHOD:
+ return assignmentTemplate(newValue, `${context}.${r.name}(${args})`);
+
+ case RECORD_TYPE_INVOKE_CLOSURE:
+ return assignmentTemplate(newValue, `${context}(${args})`);
+
+ case RECORD_TYPE_PRIMITIVE_OP:
+ return assignmentTemplate(newValue, `${UTIL}.${r.name}(${args})`);
+
+ case RECORD_TYPE_INTERPOLATE:
+ return assignmentTemplate(newValue, this.genInterpolation(r));
+
+ case RECORD_TYPE_KEYED_ACCESS:
+ var key = this.localNames[r.args[0]];
+ return assignmentTemplate(newValue, `${context}[${key}]`);
+
+ case RECORD_TYPE_INVOKE_FORMATTER:
+ return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
+
+ default:
+ throw new BaseException(`Unknown operation ${r.mode}`);
+ }
+ }
+
+ genInterpolation(r:ProtoRecord):string{
+ var res = "";
+ for (var i = 0; i < r.args.length; ++i) {
+ res += this.genLiteral(r.fixedArgs[i]);
+ res += " + ";
+ res += this.localNames[r.args[i]];
+ res += " + ";
+ }
+ res += this.genLiteral(r.fixedArgs[r.args.length]);
+ return res;
+ }
+
+ genLiteral(value):string {
+ return JSON.stringify(value);
+ }
+
+ genNotify(r):string{
+ return r.lastInGroup ? notifyTemplate(r.selfIndex - 1) : '';
+ }
+
+ genArgs(r:ProtoRecord):string {
+ return r.args.map((arg) => this.localNames[arg]).join(", ");
+ }
+}
+
+
+
+
diff --git a/modules/change_detection/src/change_detection_util.js b/modules/change_detection/src/change_detection_util.js
new file mode 100644
index 0000000000..c9a0ecb96c
--- /dev/null
+++ b/modules/change_detection/src/change_detection_util.js
@@ -0,0 +1,138 @@
+import {isPresent, isBlank, BaseException, Type} from 'facade/lang';
+import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
+import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
+import {ArrayChanges} from './array_changes';
+import {KeyValueChanges} from './keyvalue_changes';
+import {ProtoRecord} from './proto_change_detector';
+import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
+import {ChangeRecord} from './interfaces';
+
+export var uninitialized = new Object();
+
+export class SimpleChange {
+ previousValue:any;
+ currentValue:any;
+
+ constructor(previousValue:any, currentValue:any) {
+ this.previousValue = previousValue;
+ this.currentValue = currentValue;
+ }
+}
+
+export class ChangeDetectionUtil {
+ static unitialized() {
+ return uninitialized;
+ }
+
+ static arrayFn0() { return []; }
+ static arrayFn1(a1) { return [a1]; }
+ static arrayFn2(a1, a2) { return [a1, a2]; }
+ static arrayFn3(a1, a2, a3) { return [a1, a2, a3]; }
+ static arrayFn4(a1, a2, a3, a4) { return [a1, a2, a3, a4]; }
+ static arrayFn5(a1, a2, a3, a4, a5) { return [a1, a2, a3, a4, a5]; }
+ static arrayFn6(a1, a2, a3, a4, a5, a6) { return [a1, a2, a3, a4, a5, a6]; }
+ static arrayFn7(a1, a2, a3, a4, a5, a6, a7) { return [a1, a2, a3, a4, a5, a6, a7]; }
+ static arrayFn8(a1, a2, a3, a4, a5, a6, a7, a8) { return [a1, a2, a3, a4, a5, a6, a7, a8]; }
+ static arrayFn9(a1, a2, a3, a4, a5, a6, a7, a8, a9) { return [a1, a2, a3, a4, a5, a6, a7, a8, a9]; }
+
+ static operation_negate(value) {return !value;}
+ static operation_add(left, right) {return left + right;}
+ static operation_subtract(left, right) {return left - right;}
+ static operation_multiply(left, right) {return left * right;}
+ static operation_divide(left, right) {return left / right;}
+ static operation_remainder(left, right) {return left % right;}
+ static operation_equals(left, right) {return left == right;}
+ static operation_not_equals(left, right) {return left != right;}
+ static operation_less_then(left, right) {return left < right;}
+ static operation_greater_then(left, right) {return left > right;}
+ static operation_less_or_equals_then(left, right) {return left <= right;}
+ static operation_greater_or_equals_then(left, right) {return left >= right;}
+ static operation_logical_and(left, right) {return left && right;}
+ static operation_logical_or(left, right) {return left || right;}
+ static cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
+
+ static mapFn(keys:List) {
+ function buildMap(values) {
+ var res = StringMapWrapper.create();
+ for(var i = 0; i < keys.length; ++i) {
+ StringMapWrapper.set(res, keys[i], values[i]);
+ }
+ return res;
+ }
+
+ switch (keys.length) {
+ case 0: return () => [];
+ case 1: return (a1) => buildMap([a1]);
+ case 2: return (a1, a2) => buildMap([a1, a2]);
+ case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]);
+ case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
+ case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
+ case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
+ case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
+ case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
+ case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
+ default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
+ }
+ }
+
+ static keyedAccess(obj, args) {
+ return obj[args[0]];
+ }
+
+ static structuralCheck(self, context) {
+ if (isBlank(self) || self === uninitialized) {
+ if (ArrayChanges.supports(context)) {
+ self = new ArrayChanges();
+ } else if (KeyValueChanges.supports(context)) {
+ self = new KeyValueChanges();
+ }
+ }
+
+ if (isBlank(context) || context === uninitialized) {
+ return new SimpleChange(null, null);
+
+ } else {
+ if (ArrayChanges.supports(context)) {
+
+ if (self.check(context)) {
+ return new SimpleChange(null, self); // TODO: don't wrap and return self instead
+ } else {
+ return null;
+ }
+
+ } else if (KeyValueChanges.supports(context)) {
+
+ if (self.check(context)) {
+ return new SimpleChange(null, self); // TODO: don't wrap and return self instead
+ } else {
+ return null;
+ }
+
+ } else {
+ throw new BaseException(`Unsupported type (${context})`);
+ }
+ }
+ }
+
+ static findContext(name:string, c){
+ while (c instanceof ContextWithVariableBindings) {
+ if (c.hasBinding(name)) {
+ return c;
+ }
+ c = c.parent;
+ }
+ return c;
+ }
+
+ static throwOnChange(proto:ProtoRecord, change) {
+ throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
+ }
+
+ static changeRecord(memento:any, change:any):ChangeRecord {
+ return new ChangeRecord(memento, change);
+ }
+
+ static simpleChangeRecord(memento:any, previousValue:any, currentValue:any):ChangeRecord {
+ return new ChangeRecord(memento, new SimpleChange(previousValue, currentValue));
+ }
+}
diff --git a/modules/change_detection/src/dynamic_change_detector.js b/modules/change_detection/src/dynamic_change_detector.js
index b1988bc6a7..2484e92b1d 100644
--- a/modules/change_detection/src/dynamic_change_detector.js
+++ b/modules/change_detection/src/dynamic_change_detector.js
@@ -2,6 +2,9 @@ import {isPresent, isBlank, BaseException, FunctionWrapper} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
+import {AbstractChangeDetector} from './abstract_change_detector';
+import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
+
import {ArrayChanges} from './array_changes';
import {KeyValueChanges} from './keyvalue_changes';
@@ -12,76 +15,37 @@ import {
RECORD_TYPE_INVOKE_METHOD,
RECORD_TYPE_CONST,
RECORD_TYPE_INVOKE_CLOSURE,
- RECORD_TYPE_INVOKE_PURE_FUNCTION,
+ RECORD_TYPE_PRIMITIVE_OP,
+ RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_STRUCTURAL_CHECK,
+ RECORD_TYPE_INTERPOLATE,
ProtoChangeDetector
} from './proto_change_detector';
-import {ChangeDetector, ChangeRecord, ChangeDispatcher} from './interfaces';
+import {ChangeDetector, ChangeDispatcher} from './interfaces';
import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './exceptions';
-var _uninitialized = new Object();
-
-class SimpleChange {
- previousValue:any;
- currentValue:any;
-
- constructor(previousValue:any, currentValue:any) {
- this.previousValue = previousValue;
- this.currentValue = currentValue;
- }
-}
-
-export class DynamicChangeDetector extends ChangeDetector {
+export class DynamicChangeDetector extends AbstractChangeDetector {
dispatcher:any;
formatters:Map;
- children:List;
values:List;
protos:List;
- parent:ChangeDetector;
constructor(dispatcher:any, formatters:Map, protoRecords:List) {
+ super();
this.dispatcher = dispatcher;
this.formatters = formatters;
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
- ListWrapper.fill(this.values, _uninitialized);
+ ListWrapper.fill(this.values, uninitialized);
this.protos = protoRecords;
-
- this.children = [];
- }
-
- addChild(cd:ChangeDetector) {
- ListWrapper.push(this.children, cd);
- cd.parent = this;
- }
-
- removeChild(cd:ChangeDetector) {
- ListWrapper.remove(this.children, cd);
- }
-
- remove() {
- this.parent.removeChild(this);
}
setContext(context:any) {
this.values[0] = context;
}
- detectChanges() {
- this._detectChanges(false);
- }
-
- checkNoChanges() {
- this._detectChanges(true);
- }
-
- _detectChanges(throwOnChange:boolean) {
- this._detectChangesInRecords(throwOnChange);
- this._detectChangesInChildren(throwOnChange);
- }
-
- _detectChangesInRecords(throwOnChange:boolean) {
+ detectChangesInRecords(throwOnChange:boolean) {
var protos:List = this.protos;
var updatedRecords = null;
@@ -91,21 +55,16 @@ export class DynamicChangeDetector extends ChangeDetector {
var proto:ProtoRecord = protos[i];
var change = this._check(proto);
- // only when the terminal record, which ends a binding, changes
- // we need to add it to a list of changed records
- if (isPresent(change) && proto.terminal) {
- if (throwOnChange) throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
+ if (isPresent(change)) {
currentGroup = proto.groupMemento;
updatedRecords = this._addRecord(updatedRecords, proto, change);
}
- if (isPresent(updatedRecords)) {
- var lastRecordOfCurrentGroup = protos.length == i + 1 ||
- currentGroup !== protos[i + 1].groupMemento;
- if (lastRecordOfCurrentGroup) {
- this.dispatcher.onRecordChange(currentGroup, updatedRecords);
- updatedRecords = null;
- }
+ if (proto.lastInGroup && isPresent(updatedRecords)) {
+ if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, updatedRecords[0]);
+
+ this.dispatcher.onRecordChange(currentGroup, updatedRecords);
+ updatedRecords = null;
}
}
}
@@ -128,7 +87,11 @@ export class DynamicChangeDetector extends ChangeDetector {
if (!isSame(prevValue, currValue)) {
this._writeSelf(proto, currValue);
- return new SimpleChange(prevValue === _uninitialized ? null : prevValue, currValue);
+ if (proto.lastInBinding) {
+ return new SimpleChange(prevValue, currValue);
+ } else {
+ return null;
+ }
} else {
return null;
}
@@ -144,23 +107,28 @@ export class DynamicChangeDetector extends ChangeDetector {
case RECORD_TYPE_PROPERTY:
var context = this._readContext(proto);
- while (context instanceof ContextWithVariableBindings) {
- if (context.hasBinding(proto.name)) {
- return context.get(proto.name);
- }
- context = context.parent;
+ var c = ChangeDetectionUtil.findContext(proto.name, context);
+ if (c instanceof ContextWithVariableBindings) {
+ return c.get(proto.name);
+ } else {
+ var propertyGetter:Function = proto.funcOrValue;
+ return propertyGetter(c);
}
- var propertyGetter:Function = proto.funcOrValue;
- return propertyGetter(context);
+ break;
case RECORD_TYPE_INVOKE_METHOD:
var methodInvoker:Function = proto.funcOrValue;
return methodInvoker(this._readContext(proto), this._readArgs(proto));
+ case RECORD_TYPE_KEYED_ACCESS:
+ var arg = this._readArgs(proto)[0];
+ return this._readContext(proto)[arg];
+
case RECORD_TYPE_INVOKE_CLOSURE:
return FunctionWrapper.apply(this._readContext(proto), this._readArgs(proto));
- case RECORD_TYPE_INVOKE_PURE_FUNCTION:
+ case RECORD_TYPE_INTERPOLATE:
+ case RECORD_TYPE_PRIMITIVE_OP:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
case RECORD_TYPE_INVOKE_FORMATTER:
@@ -176,39 +144,16 @@ export class DynamicChangeDetector extends ChangeDetector {
var self = this._readSelf(proto);
var context = this._readContext(proto);
- if (isBlank(self) || self === _uninitialized) {
- if (ArrayChanges.supports(context)) {
- self = new ArrayChanges();
- } else if (KeyValueChanges.supports(context)) {
- self = new KeyValueChanges();
- }
+ var change = ChangeDetectionUtil.structuralCheck(self, context);
+ if (isPresent(change)) {
+ this._writeSelf(proto, change.currentValue);
}
-
- if (ArrayChanges.supports(context)) {
- if (self.check(context)) {
- this._writeSelf(proto, self);
- return new SimpleChange(null, self); // TODO: don't wrap and return self instead
- }
-
- } else if (KeyValueChanges.supports(context)) {
- if (self.check(context)) {
- this._writeSelf(proto, self);
- return new SimpleChange(null, self); // TODO: don't wrap and return self instead
- }
-
- } else if (context == null) {
- this._writeSelf(proto, null);
- return new SimpleChange(null, null);
-
- } else {
- throw new BaseException(`Unsupported type (${context})`);
- }
-
+ return change;
}
_addRecord(updatedRecords:List, proto:ProtoRecord, change):List {
// we can use a pool of change records not to create extra garbage
- var record = new ChangeRecord(proto.bindingMemento, change);
+ var record = ChangeDetectionUtil.changeRecord(proto.bindingMemento, change);
if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList;
updatedRecords[0] = record;
@@ -222,23 +167,16 @@ export class DynamicChangeDetector extends ChangeDetector {
return updatedRecords;
}
- _detectChangesInChildren(throwOnChange:boolean) {
- var children = this.children;
- for(var i = 0; i < children.length; ++i) {
- children[i]._detectChanges(throwOnChange);
- }
- }
-
_readContext(proto:ProtoRecord) {
return this.values[proto.contextIndex];
}
_readSelf(proto:ProtoRecord) {
- return this.values[proto.record_type_selfIndex];
+ return this.values[proto.selfIndex];
}
_writeSelf(proto:ProtoRecord, value) {
- this.values[proto.record_type_selfIndex] = value;
+ this.values[proto.selfIndex] = value;
}
_readArgs(proto:ProtoRecord) {
diff --git a/modules/change_detection/src/proto_change_detector.js b/modules/change_detection/src/proto_change_detector.js
index 7db0cae774..12e5b74cbf 100644
--- a/modules/change_detection/src/proto_change_detector.js
+++ b/modules/change_detection/src/proto_change_detector.js
@@ -1,4 +1,4 @@
-import {isPresent, isBlank, BaseException} from 'facade/lang';
+import {isPresent, isBlank, BaseException, Type, isString} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
import {
@@ -24,55 +24,118 @@ import {
} from './parser/ast';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
-import {ChangeDispatcher, ChangeDetector} from './interfaces';
+import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
+import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
+import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
+
+import {ArrayChanges} from './array_changes';
+import {KeyValueChanges} from './keyvalue_changes';
export const RECORD_TYPE_SELF = 0;
-export const RECORD_TYPE_PROPERTY = 1;
-export const RECORD_TYPE_INVOKE_METHOD = 2;
-export const RECORD_TYPE_CONST = 3;
-export const RECORD_TYPE_INVOKE_CLOSURE = 4;
-export const RECORD_TYPE_INVOKE_PURE_FUNCTION = 5;
-export const RECORD_TYPE_INVOKE_FORMATTER = 6;
-export const RECORD_TYPE_STRUCTURAL_CHECK = 10;
+export const RECORD_TYPE_CONST = 1;
+export const RECORD_TYPE_PRIMITIVE_OP = 2;
+export const RECORD_TYPE_PROPERTY = 3;
+export const RECORD_TYPE_INVOKE_METHOD = 4;
+export const RECORD_TYPE_INVOKE_CLOSURE = 5;
+export const RECORD_TYPE_KEYED_ACCESS = 6;
+export const RECORD_TYPE_INVOKE_FORMATTER = 7;
+export const RECORD_TYPE_STRUCTURAL_CHECK = 8;
+export const RECORD_TYPE_INTERPOLATE = 9;
export class ProtoRecord {
mode:number;
name:string;
funcOrValue:any;
args:List;
+ fixedArgs:List;
contextIndex:number;
- record_type_selfIndex:number;
+ selfIndex:number;
bindingMemento:any;
groupMemento:any;
- terminal:boolean;
+ lastInBinding:boolean;
+ lastInGroup:boolean;
expressionAsString:string;
constructor(mode:number,
name:string,
funcOrValue,
args:List,
+ fixedArgs:List,
contextIndex:number,
- record_type_selfIndex:number,
+ selfIndex:number,
bindingMemento:any,
groupMemento:any,
- terminal:boolean,
expressionAsString:string) {
this.mode = mode;
this.name = name;
this.funcOrValue = funcOrValue;
this.args = args;
+ this.fixedArgs = fixedArgs;
this.contextIndex = contextIndex;
- this.record_type_selfIndex = record_type_selfIndex;
+ this.selfIndex = selfIndex;
this.bindingMemento = bindingMemento;
this.groupMemento = groupMemento;
- this.terminal = terminal;
+ this.lastInBinding = false;
+ this.lastInGroup = false;
this.expressionAsString = expressionAsString;
}
}
-export class ProtoChangeDetector {
+export class ProtoChangeDetector {
+ addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false){}
+ instantiate(dispatcher:any, formatters:Map):ChangeDetector{
+ return null;
+ }
+}
+
+export class DynamicProtoChangeDetector extends ProtoChangeDetector {
+ _recordBuilder:ProtoRecordBuilder;
+
+ constructor() {
+ this._recordBuilder = new ProtoRecordBuilder();
+ }
+
+ addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
+ this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
+ }
+
+ instantiate(dispatcher:any, formatters:Map) {
+ var records = this._recordBuilder.records;
+ return new DynamicChangeDetector(dispatcher, formatters, records);
+ }
+}
+
+var _jitProtoChangeDetectorClassCounter:number = 0;
+export class JitProtoChangeDetector extends ProtoChangeDetector {
+ _factory:Function;
+ _recordBuilder:ProtoRecordBuilder;
+
+ constructor() {
+ this._recordBuilder = new ProtoRecordBuilder();
+ }
+
+ addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
+ this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
+ }
+
+ instantiate(dispatcher:any, formatters:Map) {
+ this._createFactoryIfNecessary();
+ return this._factory(dispatcher, formatters);
+ }
+
+ _createFactoryIfNecessary() {
+ if (isBlank(this._factory)) {
+ var c = _jitProtoChangeDetectorClassCounter++;
+ var records = this._recordBuilder.records;
+ var typeName = `ChangeDetector${c}`;
+ this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
+ }
+ }
+}
+
+class ProtoRecordBuilder {
records:List;
constructor() {
@@ -82,23 +145,23 @@ export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
if (structural) ast = new Structural(ast);
- var c = new ProtoOperationsCreator(bindingMemento, groupMemento,
- this.records.length, ast.toString());
- ast.visit(c);
-
- if (! ListWrapper.isEmpty(c.protoRecords)) {
- var last = ListWrapper.last(c.protoRecords);
- last.terminal = true;
- this.records = ListWrapper.concat(this.records, c.protoRecords);
+ var last = ListWrapper.last(this.records);
+ if (isPresent(last) && last.groupMemento == groupMemento) {
+ last.lastInGroup = false;
}
- }
- instantiate(dispatcher:any, formatters:Map) {
- return new DynamicChangeDetector(dispatcher, formatters, this.records);
+ var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, groupMemento, this.records.length);
+ if (! ListWrapper.isEmpty(pr)) {
+ var last = ListWrapper.last(pr);
+ last.lastInBinding = true;
+ last.lastInGroup = true;
+
+ this.records = ListWrapper.concat(this.records, pr);
+ }
}
}
-class ProtoOperationsCreator {
+class _ConvertAstIntoProtoRecords {
protoRecords:List;
bindingMemento:any;
groupMemento:any;
@@ -113,77 +176,89 @@ class ProtoOperationsCreator {
this.expressionAsString = expressionAsString;
}
+ static convert(ast:AST, bindingMemento:any, groupMemento:any, contextIndex:number) {
+ var c = new _ConvertAstIntoProtoRecords(bindingMemento, groupMemento, contextIndex, ast.toString());
+ ast.visit(c);
+ return c.protoRecords;
+ }
+
visitImplicitReceiver(ast:ImplicitReceiver) {
return 0;
}
visitInterpolation(ast:Interpolation) {
var args = this._visitAll(ast.expressions);
- return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Interpolate()", _interpolationFn(ast.strings), args, 0);
+ return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
+ args, ast.strings, 0);
}
visitLiteralPrimitive(ast:LiteralPrimitive) {
- return this._addRecord(RECORD_TYPE_CONST, null, ast.value, [], 0);
+ return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
}
visitAccessMember(ast:AccessMember) {
var receiver = ast.receiver.visit(this);
- return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], receiver);
+ return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
}
visitFormatter(ast:Formatter) {
- return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), 0);
+ return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), null, 0);
}
visitMethodCall(ast:MethodCall) {
var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args);
- return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, receiver);
+ return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, null, receiver);
}
visitFunctionCall(ast:FunctionCall) {
var target = ast.target.visit(this);
var args = this._visitAll(ast.args);
- return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, null, null, args, target);
+ return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
}
visitLiteralArray(ast:LiteralArray) {
- return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Array()", _arrayFn(ast.expressions.length),
- this._visitAll(ast.expressions), 0);
+ var primitiveName = `arrayFn${ast.expressions.length}`;
+ return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
+ this._visitAll(ast.expressions), null, 0);
}
visitLiteralMap(ast:LiteralMap) {
- return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Map()", _mapFn(ast.keys, ast.values.length),
- this._visitAll(ast.values), 0);
+ return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
+ ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0);
}
visitBinary(ast:Binary) {
var left = ast.left.visit(this);
var right = ast.right.visit(this);
- return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, ast.operation, _operationToFunction(ast.operation), [left, right], 0);
+ return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
+ _operationToFunction(ast.operation), [left, right], null, 0);
}
visitPrefixNot(ast:PrefixNot) {
var exp = ast.expression.visit(this)
- return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "-", _operation_negate, [exp], 0);
+ return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
+ ChangeDetectionUtil.operation_negate, [exp], null, 0);
}
visitConditional(ast:Conditional) {
var c = ast.condition.visit(this);
var t = ast.trueExp.visit(this);
var f = ast.falseExp.visit(this);
- return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "?:", _cond, [c,t,f], 0);
+ return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "cond",
+ ChangeDetectionUtil.cond, [c,t,f], null, 0);
}
visitStructural(ast:Structural) {
var value = ast.value.visit(this);
- return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "record_type_structural_check", null, [], value);
+ return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
}
visitKeyedAccess(ast:KeyedAccess) {
var obj = ast.obj.visit(this);
var key = ast.key.visit(this);
- return this._addRecord(RECORD_TYPE_INVOKE_METHOD, "[]", _keyedAccess, [key], obj);
+ return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess",
+ ChangeDetectionUtil.keyedAccess, [key], null, obj);
}
_visitAll(asts:List) {
@@ -194,111 +269,93 @@ class ProtoOperationsCreator {
return res;
}
- _addRecord(type, name, funcOrValue, args, context) {
- var record_type_selfIndex = ++ this.contextIndex;
+ _addRecord(type, name, funcOrValue, args, fixedArgs, context) {
+ var selfIndex = ++ this.contextIndex;
ListWrapper.push(this.protoRecords,
- new ProtoRecord(type, name, funcOrValue, args, context, record_type_selfIndex,
- this.bindingMemento, this.groupMemento, false, this.expressionAsString));
- return record_type_selfIndex;
+ new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
+ this.bindingMemento, this.groupMemento, this.expressionAsString));
+ return selfIndex;
}
}
-function _arrayFn(length:int) {
- switch (length) {
- case 0: return () => [];
- case 1: return (a1) => [a1];
- case 2: return (a1, a2) => [a1, a2];
- case 3: return (a1, a2, a3) => [a1, a2, a3];
- case 4: return (a1, a2, a3, a4) => [a1, a2, a3, a4];
- case 5: return (a1, a2, a3, a4, a5) => [a1, a2, a3, a4, a5];
- case 6: return (a1, a2, a3, a4, a5, a6) => [a1, a2, a3, a4, a5, a6];
- case 7: return (a1, a2, a3, a4, a5, a6, a7) => [a1, a2, a3, a4, a5, a6, a7];
- case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => [a1, a2, a3, a4, a5, a6, a7, a8];
- case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => [a1, a2, a3, a4, a5, a6, a7, a8, a9];
- default: throw new BaseException(`Does not support literal arrays with more than 9 elements`);
- }
-}
-
-function _mapFn(keys:List, length:int) {
- function buildMap(values) {
- var res = StringMapWrapper.create();
- for(var i = 0; i < keys.length; ++i) {
- StringMapWrapper.set(res, keys[i], values[i]);
- }
- return res;
- }
+function _arrayFn(length:number):Function {
switch (length) {
- case 0: return () => [];
- case 1: return (a1) => buildMap([a1]);
- case 2: return (a1, a2) => buildMap([a1, a2]);
- case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]);
- case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
- case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
- case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
- case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
- case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
- case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
+ case 0: return ChangeDetectionUtil.arrayFn0;
+ case 1: return ChangeDetectionUtil.arrayFn1;
+ case 2: return ChangeDetectionUtil.arrayFn2;
+ case 3: return ChangeDetectionUtil.arrayFn3;
+ case 4: return ChangeDetectionUtil.arrayFn4;
+ case 5: return ChangeDetectionUtil.arrayFn5;
+ case 6: return ChangeDetectionUtil.arrayFn6;
+ case 7: return ChangeDetectionUtil.arrayFn7;
+ case 8: return ChangeDetectionUtil.arrayFn8;
+ case 9: return ChangeDetectionUtil.arrayFn9;
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
+function _mapPrimitiveName(keys:List) {
+ var stringifiedKeys = ListWrapper.join(
+ ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`),
+ ", ");
+ return `mapFn([${stringifiedKeys}])`;
+}
+
+function _operationToPrimitiveName(operation:string):string {
+ switch(operation) {
+ case '+' : return "operation_add";
+ case '-' : return "operation_subtract";
+ case '*' : return "operation_multiply";
+ case '/' : return "operation_divide";
+ case '%' : return "operation_remainder";
+ case '==' : return "operation_equals";
+ case '!=' : return "operation_not_equals";
+ case '<' : return "operation_less_then";
+ case '>' : return "operation_greater_then";
+ case '<=' : return "operation_less_or_equals_then";
+ case '>=' : return "operation_greater_or_equals_then";
+ case '&&' : return "operation_logical_and";
+ case '||' : return "operation_logical_or";
+ default: throw new BaseException(`Unsupported operation ${operation}`);
+ }
+}
+
function _operationToFunction(operation:string):Function {
switch(operation) {
- case '+' : return _operation_add;
- case '-' : return _operation_subtract;
- case '*' : return _operation_multiply;
- case '/' : return _operation_divide;
- case '%' : return _operation_remainder;
- case '==' : return _operation_equals;
- case '!=' : return _operation_not_equals;
- case '<' : return _operation_less_then;
- case '>' : return _operation_greater_then;
- case '<=' : return _operation_less_or_equals_then;
- case '>=' : return _operation_greater_or_equals_then;
- case '&&' : return _operation_logical_and;
- case '||' : return _operation_logical_or;
+ case '+' : return ChangeDetectionUtil.operation_add;
+ case '-' : return ChangeDetectionUtil.operation_subtract;
+ case '*' : return ChangeDetectionUtil.operation_multiply;
+ case '/' : return ChangeDetectionUtil.operation_divide;
+ case '%' : return ChangeDetectionUtil.operation_remainder;
+ case '==' : return ChangeDetectionUtil.operation_equals;
+ case '!=' : return ChangeDetectionUtil.operation_not_equals;
+ case '<' : return ChangeDetectionUtil.operation_less_then;
+ case '>' : return ChangeDetectionUtil.operation_greater_then;
+ case '<=' : return ChangeDetectionUtil.operation_less_or_equals_then;
+ case '>=' : return ChangeDetectionUtil.operation_greater_or_equals_then;
+ case '&&' : return ChangeDetectionUtil.operation_logical_and;
+ case '||' : return ChangeDetectionUtil.operation_logical_or;
default: throw new BaseException(`Unsupported operation ${operation}`);
}
}
-function _operation_negate(value) {return !value;}
-function _operation_add(left, right) {return left + right;}
-function _operation_subtract(left, right) {return left - right;}
-function _operation_multiply(left, right) {return left * right;}
-function _operation_divide(left, right) {return left / right;}
-function _operation_remainder(left, right) {return left % right;}
-function _operation_equals(left, right) {return left == right;}
-function _operation_not_equals(left, right) {return left != right;}
-function _operation_less_then(left, right) {return left < right;}
-function _operation_greater_then(left, right) {return left > right;}
-function _operation_less_or_equals_then(left, right) {return left <= right;}
-function _operation_greater_or_equals_then(left, right) {return left >= right;}
-function _operation_logical_and(left, right) {return left && right;}
-function _operation_logical_or(left, right) {return left || right;}
-function _cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
-
-function _keyedAccess(obj, args) {
- return obj[args[0]];
-}
-
function s(v) {
return isPresent(v) ? '' + v : '';
}
function _interpolationFn(strings:List) {
var length = strings.length;
- var i = -1;
- var c0 = length > ++i ? strings[i] : null;
- var c1 = length > ++i ? strings[i] : null;
- var c2 = length > ++i ? strings[i] : null;
- var c3 = length > ++i ? strings[i] : null;
- var c4 = length > ++i ? strings[i] : null;
- var c5 = length > ++i ? strings[i] : null;
- var c6 = length > ++i ? strings[i] : null;
- var c7 = length > ++i ? strings[i] : null;
- var c8 = length > ++i ? strings[i] : null;
- var c9 = length > ++i ? strings[i] : null;
+ var c0 = length > 0 ? strings[0] : null;
+ var c1 = length > 1 ? strings[1] : null;
+ var c2 = length > 2 ? strings[2] : null;
+ var c3 = length > 3 ? strings[3] : null;
+ var c4 = length > 4 ? strings[4] : null;
+ var c5 = length > 5 ? strings[5] : null;
+ var c6 = length > 6 ? strings[6] : null;
+ var c7 = length > 7 ? strings[7] : null;
+ var c8 = length > 8 ? strings[8] : null;
+ var c9 = length > 9 ? strings[9] : null;
switch (length - 1) {
case 1: return (a1) => c0 + s(a1) + c1;
case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
@@ -311,4 +368,4 @@ function _interpolationFn(strings:List) {
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9;
default: throw new BaseException(`Does not support more than 9 expressions`);
}
-}
+}
\ No newline at end of file
diff --git a/modules/change_detection/test/change_detection_spec.js b/modules/change_detection/test/change_detection_spec.js
index cbfae7f09e..905f665be6 100644
--- a/modules/change_detection/test/change_detection_spec.js
+++ b/modules/change_detection/test/change_detection_spec.js
@@ -1,4 +1,4 @@
-import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'test_lib/test_lib';
+import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DARTIUM} from 'test_lib/test_lib';
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
@@ -8,398 +8,442 @@ import {Lexer} from 'change_detection/parser/lexer';
import {reflector} from 'reflection/reflection';
import {arrayChangesAsString, kvChangesAsString} from './util';
-import {ProtoChangeDetector, ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError,
- ContextWithVariableBindings}
+import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWithVariableBindings}
from 'change_detection/change_detection';
+import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'change_detection/proto_change_detector';
+
+
export function main() {
- function ast(exp:string, location:string = 'location') {
- var parser = new Parser(new Lexer());
- return parser.parseBinding(exp, location);
- }
+ describe("change detection", () => {
+ StringMapWrapper.forEach(
+ { "dynamic": () => new DynamicProtoChangeDetector(),
+ "JIT": () => new JitProtoChangeDetector()
+ }, (createProtoChangeDetector, name) => {
- function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
- structural = false) {
- var pcd = new ProtoChangeDetector();
- pcd.addAst(ast(exp), memo, memo, structural);
+ if (name == "JIT" && IS_DARTIUM) return;
- var dispatcher = new TestDispatcher();
- var cd = pcd.instantiate(dispatcher, formatters);
- cd.setContext(context);
-
- return {"changeDetector" : cd, "dispatcher" : dispatcher};
- }
-
- function executeWatch(memo:string, exp:string, context = null, formatters = null,
- content = false) {
- var res = createChangeDetector(memo, exp, context, formatters, content);
- res["changeDetector"].detectChanges();
- return res["dispatcher"].log;
- }
-
- describe('change_detection', () => {
- it('should do simple watching', () => {
- var person = new Person("misko");
- var c = createChangeDetector('name', 'name', person);
- var cd = c["changeDetector"];
- var dispatcher = c["dispatcher"];
-
- cd.detectChanges();
- expect(dispatcher.log).toEqual(['name=misko']);
-
- dispatcher.clear();
- cd.detectChanges();
- expect(dispatcher.log).toEqual([]);
-
- person.name = "Misko";
- cd.detectChanges();
- expect(dispatcher.log).toEqual(['name=Misko']);
- });
-
- it('should report all changes on the first run including uninitialized values', () => {
- var uninit = new Uninitialized();
- var c = createChangeDetector('value', 'value', uninit);
- var cd = c["changeDetector"];
- var dispatcher = c["dispatcher"];
-
- cd.detectChanges();
- expect(dispatcher.log).toEqual(['value=null']);
- });
-
- it("should support literals", () => {
- expect(executeWatch('const', '10')).toEqual(['const=10']);
- expect(executeWatch('const', '"str"')).toEqual(['const=str']);
- });
-
- it('simple chained property access', () => {
- var address = new Address('Grenoble');
- var person = new Person('Victor', address);
-
- expect(executeWatch('address.city', 'address.city', person))
- .toEqual(['address.city=Grenoble']);
- });
-
- it("should support method calls", () => {
- var person = new Person('Victor');
- expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']);
- });
-
- it("should support function calls", () => {
- var td = new TestData(() => (a) => a);
- expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']);
- });
-
- it("should support chained method calls", () => {
- var person = new Person('Victor');
- var td = new TestData(person);
- expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']);
- });
-
- it("should support literal array", () => {
- var c = createChangeDetector('array', '[1,2]');
- c["changeDetector"].detectChanges();
- 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]]]);
- });
-
- it("should support literal maps", () => {
- var c = createChangeDetector('map', '{z:1}');
- c["changeDetector"].detectChanges();
- expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
-
- c = createChangeDetector('map', '{z:a}', new TestData(1));
- c["changeDetector"].detectChanges();
- expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
- });
-
- it("should support binary operations", () => {
- expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']);
- expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']);
-
- expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']);
- expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5
- expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']);
-
- expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']);
- expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']);
-
- expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']);
- expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
-
- expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']);
- expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
-
- expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']);
- expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']);
- expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']);
-
- expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']);
- expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']);
- expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']);
-
- expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']);
- expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']);
-
- expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']);
- expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']);
- });
-
- it("should support negate", () => {
- expect(executeWatch('exp', '!true')).toEqual(['exp=false']);
- expect(executeWatch('exp', '!!true')).toEqual(['exp=true']);
- });
-
- it("should support conditionals", () => {
- expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']);
- expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
- });
-
- describe("keyed access", () => {
- it("should support accessing a list item", () => {
- expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']);
- });
-
- it("should support accessing a map item", () => {
- expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']);
- });
- });
-
- it("should support formatters", () => {
- var formatters = MapWrapper.createFromPairs([
- ['uppercase', (v) => v.toUpperCase()],
- ['wrap', (v, before, after) => `${before}${v}${after}`]]);
- expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
- expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
- });
-
- describe("group changes", () => {
- it("should notify the dispatcher when a group of records changes", () => {
- var pcd = new ProtoChangeDetector();
- pcd.addAst(ast("1 + 2"), "memo", 1);
- pcd.addAst(ast("10 + 20"), "memo", 1);
- pcd.addAst(ast("100 + 200"), "memo2", 2);
-
- var dispatcher = new TestDispatcher();
- var cd = pcd.instantiate(dispatcher, null);
-
- cd.detectChanges();
-
- expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]);
- });
-
- it("should update every instance of a group individually", () => {
- var pcd = new ProtoChangeDetector();
- pcd.addAst(ast("1 + 2"), "memo", "memo");
-
- var dispatcher = new TestDispatcher();
- var cd = new DynamicChangeDetector(dispatcher, null, []);
- cd.addChild(pcd.instantiate(dispatcher, null));
- cd.addChild(pcd.instantiate(dispatcher, null));
-
- cd.detectChanges();
-
- expect(dispatcher.loggedValues).toEqual([[3], [3]]);
- });
-
- it("should notify the dispatcher before switching to the next group", () => {
- var pcd = new ProtoChangeDetector();
- pcd.addAst(ast("a()"), "a", 1);
- pcd.addAst(ast("b()"), "b", 2);
- pcd.addAst(ast("c()"), "c", 2);
-
- var dispatcher = new TestDispatcher();
- var cd = pcd.instantiate(dispatcher, null);
-
- 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.setContext(tr);
-
- cd.detectChanges();
-
- expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
- });
- });
-
- describe("enforce no new changes", () => {
- it("should throw when a record gets changed after it has been checked", () => {
- var pcd = new ProtoChangeDetector();
- pcd.addAst(ast("a"), "a", 1);
-
- var dispatcher = new TestDispatcher();
- var cd = pcd.instantiate(dispatcher, null);
- cd.setContext(new TestData('value'));
-
- expect(() => {
- cd.checkNoChanges();
- }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked"));
- });
- });
-
- describe("error handling", () => {
- it("should wrap exceptions into ChangeDetectionError", () => {
- var pcd = new ProtoChangeDetector();
- pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
-
- var cd = pcd.instantiate(new TestDispatcher(), null);
- cd.setContext(null);
-
- try {
- cd.detectChanges();
- throw new BaseException("fail");
- } catch (e) {
- expect(e).toBeAnInstanceOf(ChangeDetectionError);
- expect(e.location).toEqual("invalidProp in someComponent");
+ function ast(exp:string, location:string = 'location') {
+ var parser = new Parser(new Lexer());
+ return parser.parseBinding(exp, location);
}
- });
- });
- describe("collections", () => {
- it("should support null values", () => {
- var context = new TestData(null);
+ function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
+ structural = false) {
+ var pcd = createProtoChangeDetector();
+ pcd.addAst(ast(exp), memo, memo, structural);
- var c = createChangeDetector('a', 'a', context, null, true);
- var cd = c["changeDetector"];
- var dispatcher = c["dispatcher"];
+ var dispatcher = new TestDispatcher();
+ var cd = pcd.instantiate(dispatcher, formatters);
+ cd.setContext(context);
- cd.detectChanges();
- expect(dispatcher.log).toEqual(['a=null']);
- dispatcher.clear();
+ return {"changeDetector" : cd, "dispatcher" : dispatcher};
+ }
- //cd.detectChanges();
- //expect(dispatcher.log).toEqual([]);
+ function executeWatch(memo:string, exp:string, context = null, formatters = null,
+ content = false) {
+ var res = createChangeDetector(memo, exp, context, formatters, content);
+ res["changeDetector"].detectChanges();
+ return res["dispatcher"].log;
+ }
- context.a = [0];
- cd.detectChanges();
+ describe(`${name} change detection`, () => {
+ it('should do simple watching', () => {
+ var person = new Person("misko");
+ var c = createChangeDetector('name', 'name', person);
+ var cd = c["changeDetector"];
+ var dispatcher = c["dispatcher"];
- expect(dispatcher.log).toEqual(["a=" +
- arrayChangesAsString({
- collection: ['0[null->0]'],
- additions: ['0[null->0]']
- })
- ]);
- dispatcher.clear();
+ cd.detectChanges();
+ expect(dispatcher.log).toEqual(['name=misko']);
+ dispatcher.clear();
- context.a = null;
- cd.detectChanges();
- expect(dispatcher.log).toEqual(['a=null']);
- });
+ person.name = "Misko";
+ cd.detectChanges();
+ expect(dispatcher.log).toEqual(['name=Misko']);
+ });
- describe("list", () => {
- it("should support list changes", () => {
- var context = new TestData([1, 2]);
+ it('should report all changes on the first run including uninitialized values', () => {
+ expect(executeWatch('value', 'value', new Uninitialized())).toEqual(['value=null']);
+ });
- expect(executeWatch("a", "a", context, null, true))
- .toEqual(["a=" +
- arrayChangesAsString({
- collection: ['1[null->0]', '2[null->1]'],
- additions: ['1[null->0]', '2[null->1]']
- })]);
- });
+ it('should report all changes on the first run including null values', () => {
+ var td = new TestData(null);
+ expect(executeWatch('a', 'a', td)).toEqual(['a=null']);
+ });
- it("should handle reference changes", () => {
- var context = new TestData([1, 2]);
- var objs = createChangeDetector("a", "a", context, null, true);
- var cd = objs["changeDetector"];
- var dispatcher = objs["dispatcher"];
- cd.detectChanges();
- dispatcher.clear();
+ it("should support literals", () => {
+ expect(executeWatch('const', '10')).toEqual(['const=10']);
+ expect(executeWatch('const', '"str"')).toEqual(['const=str']);
+ expect(executeWatch('const', '"a\n\nb"')).toEqual(['const=a\n\nb']);
+ });
- context.a = [2, 1];
- cd.detectChanges();
- expect(dispatcher.log).toEqual(["a=" +
- arrayChangesAsString({
- collection: ['2[1->0]', '1[0->1]'],
- previous: ['1[0->1]', '2[1->0]'],
- moves: ['2[1->0]', '1[0->1]']
- })]);
- });
- });
+ it('simple chained property access', () => {
+ var address = new Address('Grenoble');
+ var person = new Person('Victor', address);
- describe("map", () => {
- it("should support map changes", () => {
- var map = MapWrapper.create();
- MapWrapper.set(map, "foo", "bar");
- var context = new TestData(map);
- expect(executeWatch("a", "a", context, null, true))
- .toEqual(["a=" +
- kvChangesAsString({
- map: ['foo[null->bar]'],
- additions: ['foo[null->bar]']
- })]);
- });
+ expect(executeWatch('address.city', 'address.city', person))
+ .toEqual(['address.city=Grenoble']);
+ });
- it("should handle reference changes", () => {
- var map = MapWrapper.create();
- MapWrapper.set(map, "foo", "bar");
- var context = new TestData(map);
- var objs = createChangeDetector("a", "a", context, null, true);
- var cd = objs["changeDetector"];
- var dispatcher = objs["dispatcher"];
- cd.detectChanges();
- dispatcher.clear();
+ it("should support method calls", () => {
+ var person = new Person('Victor');
+ expect(executeWatch('m', 'sayHi("Jim")', person)).toEqual(['m=Hi, Jim']);
+ });
- context.a = MapWrapper.create();
- MapWrapper.set(context.a, "bar", "foo");
- cd.detectChanges();
- expect(dispatcher.log).toEqual(["a=" +
- kvChangesAsString({
- map: ['bar[null->foo]'],
- previous: ['foo[bar->null]'],
- additions: ['bar[null->foo]'],
- removals: ['foo[bar->null]']
- })]);
- });
- });
+ it("should support function calls", () => {
+ var td = new TestData(() => (a) => a);
+ expect(executeWatch('value', 'a()(99)', td)).toEqual(['value=99']);
+ });
- if (isJsObject({})) {
- describe("js objects", () => {
- it("should support object changes", () => {
- var map = {"foo": "bar"};
- var context = new TestData(map);
- expect(executeWatch("a", "a", context, null, true))
- .toEqual(["a=" +
- kvChangesAsString({
- map: ['foo[null->bar]'],
- additions: ['foo[null->bar]']
- })]);
+ it("should support chained method calls", () => {
+ var person = new Person('Victor');
+ var td = new TestData(person);
+ expect(executeWatch('m', 'a.sayHi("Jim")', td)).toEqual(['m=Hi, Jim']);
+ });
+
+ it("should support literal array", () => {
+ var c = createChangeDetector('array', '[1,2]');
+ c["changeDetector"].detectChanges();
+ 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]]]);
+ });
+
+ it("should support literal maps", () => {
+ var c = createChangeDetector('map', '{z:1}');
+ c["changeDetector"].detectChanges();
+ expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
+
+ c = createChangeDetector('map', '{z:a}', new TestData(1));
+ c["changeDetector"].detectChanges();
+ expect(c["dispatcher"].loggedValues[0][0]['z']).toEqual(1);
+ });
+
+ it("should support binary operations", () => {
+ expect(executeWatch('exp', '10 + 2')).toEqual(['exp=12']);
+ expect(executeWatch('exp', '10 - 2')).toEqual(['exp=8']);
+
+ expect(executeWatch('exp', '10 * 2')).toEqual(['exp=20']);
+ expect(executeWatch('exp', '10 / 2')).toEqual([`exp=${5.0}`]); //dart exp=5.0, js exp=5
+ expect(executeWatch('exp', '11 % 2')).toEqual(['exp=1']);
+
+ expect(executeWatch('exp', '1 == 1')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '1 != 1')).toEqual(['exp=false']);
+
+ expect(executeWatch('exp', '1 < 2')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
+
+ expect(executeWatch('exp', '2 > 1')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '2 < 1')).toEqual(['exp=false']);
+
+ expect(executeWatch('exp', '1 <= 2')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '2 <= 2')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '2 <= 1')).toEqual(['exp=false']);
+
+ expect(executeWatch('exp', '2 >= 1')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '2 >= 2')).toEqual(['exp=true']);
+ expect(executeWatch('exp', '1 >= 2')).toEqual(['exp=false']);
+
+ expect(executeWatch('exp', 'true && true')).toEqual(['exp=true']);
+ expect(executeWatch('exp', 'true && false')).toEqual(['exp=false']);
+
+ expect(executeWatch('exp', 'true || false')).toEqual(['exp=true']);
+ expect(executeWatch('exp', 'false || false')).toEqual(['exp=false']);
+ });
+
+ it("should support negate", () => {
+ expect(executeWatch('exp', '!true')).toEqual(['exp=false']);
+ expect(executeWatch('exp', '!!true')).toEqual(['exp=true']);
+ });
+
+ it("should support conditionals", () => {
+ expect(executeWatch('m', '1 < 2 ? 1 : 2')).toEqual(['m=1']);
+ expect(executeWatch('m', '1 > 2 ? 1 : 2')).toEqual(['m=2']);
+ });
+
+ describe("keyed access", () => {
+ it("should support accessing a list item", () => {
+ expect(executeWatch('array[0]', '["foo", "bar"][0]')).toEqual(['array[0]=foo']);
+ });
+
+ it("should support accessing a map item", () => {
+ expect(executeWatch('map[foo]', '{"foo": "bar"}["foo"]')).toEqual(['map[foo]=bar']);
+ });
+ });
+
+ it("should support formatters", () => {
+ var formatters = MapWrapper.createFromPairs([
+ ['uppercase', (v) => v.toUpperCase()],
+ ['wrap', (v, before, after) => `${before}${v}${after}`]]);
+ expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
+ expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
+ });
+
+ it("should support interpolation", () => {
+ var parser = new Parser(new Lexer());
+ var pcd = createProtoChangeDetector();
+ var ast = parser.parseInterpolation("B{{a}}A", "location");
+ pcd.addAst(ast, "memo", "memo", false);
+
+ var dispatcher = new TestDispatcher();
+ var cd = pcd.instantiate(dispatcher, MapWrapper.create());
+ cd.setContext(new TestData("value"));
+
+ cd.detectChanges();
+
+ expect(dispatcher.log).toEqual(["memo=BvalueA"]);
+ });
+
+ describe("group changes", () => {
+ it("should notify the dispatcher when a group of records changes", () => {
+ var pcd = createProtoChangeDetector();
+ pcd.addAst(ast("1 + 2"), "memo", "1");
+ pcd.addAst(ast("10 + 20"), "memo", "1");
+ pcd.addAst(ast("100 + 200"), "memo2", "2");
+
+ var dispatcher = new TestDispatcher();
+ var cd = pcd.instantiate(dispatcher, null);
+
+ cd.detectChanges();
+
+ expect(dispatcher.loggedValues).toEqual([[3, 30], [300]]);
+ });
+
+ it("should notify the dispatcher before switching to the next group", () => {
+ var pcd = createProtoChangeDetector();
+ pcd.addAst(ast("a()"), "a", "1");
+ pcd.addAst(ast("b()"), "b", "2");
+ pcd.addAst(ast("c()"), "c", "2");
+
+ var dispatcher = new TestDispatcher();
+ var cd = pcd.instantiate(dispatcher, null);
+
+ 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.setContext(tr);
+
+ cd.detectChanges();
+
+ expect(dispatcher.loggedValues).toEqual(['InvokeA', ['a'], 'InvokeB', 'InvokeC', ['b', 'c']]);
+ });
+ });
+
+ describe("enforce no new changes", () => {
+ it("should throw when a record gets changed after it has been checked", () => {
+ var pcd = createProtoChangeDetector();
+ pcd.addAst(ast("a"), "a", 1);
+
+ var dispatcher = new TestDispatcher();
+ var cd = pcd.instantiate(dispatcher, null);
+ cd.setContext(new TestData('value'));
+
+ expect(() => {
+ cd.checkNoChanges();
+ }).toThrowError(new RegExp("Expression 'a in location' has changed after it was checked"));
+ });
+ });
+
+ //TODO vsavkin: implement it
+ describe("error handling", () => {
+ xit("should wrap exceptions into ChangeDetectionError", () => {
+ var pcd = createProtoChangeDetector();
+ pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
+
+ var cd = pcd.instantiate(new TestDispatcher(), null);
+ cd.setContext(null);
+
+ try {
+ cd.detectChanges();
+
+ throw new BaseException("fail");
+ } catch (e) {
+ expect(e).toBeAnInstanceOf(ChangeDetectionError);
+ expect(e.location).toEqual("invalidProp in someComponent");
+ }
+ });
+ });
+
+ describe("collections", () => {
+ it("should support null values", () => {
+ var context = new TestData(null);
+
+ var c = createChangeDetector('a', 'a', context, null, true);
+ var cd = c["changeDetector"];
+ var dispatcher = c["dispatcher"];
+
+ cd.detectChanges();
+ expect(dispatcher.log).toEqual(['a=null']);
+ dispatcher.clear();
+
+ context.a = [0];
+ cd.detectChanges();
+
+ expect(dispatcher.log).toEqual(["a=" +
+ arrayChangesAsString({
+ collection: ['0[null->0]'],
+ additions: ['0[null->0]']
+ })
+ ]);
+ dispatcher.clear();
+
+ context.a = null;
+ cd.detectChanges();
+ expect(dispatcher.log).toEqual(['a=null']);
+ });
+
+ describe("list", () => {
+ it("should support list changes", () => {
+ var context = new TestData([1, 2]);
+
+ expect(executeWatch("a", "a", context, null, true))
+ .toEqual(["a=" +
+ arrayChangesAsString({
+ collection: ['1[null->0]', '2[null->1]'],
+ additions: ['1[null->0]', '2[null->1]']
+ })]);
+ });
+
+ it("should handle reference changes", () => {
+ var context = new TestData([1, 2]);
+ var objs = createChangeDetector("a", "a", context, null, true);
+ var cd = objs["changeDetector"];
+ var dispatcher = objs["dispatcher"];
+ cd.detectChanges();
+ dispatcher.clear();
+
+ context.a = [2, 1];
+ cd.detectChanges();
+ expect(dispatcher.log).toEqual(["a=" +
+ arrayChangesAsString({
+ collection: ['2[1->0]', '1[0->1]'],
+ previous: ['1[0->1]', '2[1->0]'],
+ moves: ['2[1->0]', '1[0->1]']
+ })]);
+ });
+ });
+
+ describe("map", () => {
+ it("should support map changes", () => {
+ var map = MapWrapper.create();
+ MapWrapper.set(map, "foo", "bar");
+ var context = new TestData(map);
+ expect(executeWatch("a", "a", context, null, true))
+ .toEqual(["a=" +
+ kvChangesAsString({
+ map: ['foo[null->bar]'],
+ additions: ['foo[null->bar]']
+ })]);
+ });
+
+ it("should handle reference changes", () => {
+ var map = MapWrapper.create();
+ MapWrapper.set(map, "foo", "bar");
+ var context = new TestData(map);
+ var objs = createChangeDetector("a", "a", context, null, true);
+ var cd = objs["changeDetector"];
+ var dispatcher = objs["dispatcher"];
+ cd.detectChanges();
+ dispatcher.clear();
+
+ context.a = MapWrapper.create();
+ MapWrapper.set(context.a, "bar", "foo");
+ cd.detectChanges();
+ expect(dispatcher.log).toEqual(["a=" +
+ kvChangesAsString({
+ map: ['bar[null->foo]'],
+ previous: ['foo[bar->null]'],
+ additions: ['bar[null->foo]'],
+ removals: ['foo[bar->null]']
+ })]);
+ });
+ });
+
+ if (!IS_DARTIUM) {
+ describe("js objects", () => {
+ it("should support object changes", () => {
+ var map = {"foo": "bar"};
+ var context = new TestData(map);
+ expect(executeWatch("a", "a", context, null, true))
+ .toEqual(["a=" +
+ kvChangesAsString({
+ map: ['foo[null->bar]'],
+ additions: ['foo[null->bar]']
+ })]);
+ });
+ });
+ }
+ });
+
+ describe("ContextWithVariableBindings", () => {
+ it('should read a field from ContextWithVariableBindings', () => {
+ var locals = new ContextWithVariableBindings(null,
+ MapWrapper.createFromPairs([["key", "value"]]));
+
+ expect(executeWatch('key', 'key', locals))
+ .toEqual(['key=value']);
+ });
+
+ it('should handle nested ContextWithVariableBindings', () => {
+ var nested = new ContextWithVariableBindings(null,
+ MapWrapper.createFromPairs([["key", "value"]]));
+ var locals = new ContextWithVariableBindings(nested, MapWrapper.create());
+
+ expect(executeWatch('key', 'key', locals))
+ .toEqual(['key=value']);
+ });
+
+ it("should fall back to a regular field read when ContextWithVariableBindings " +
+ "does not have the requested field", () => {
+ var locals = new ContextWithVariableBindings(new Person("Jim"),
+ MapWrapper.createFromPairs([["key", "value"]]));
+
+ expect(executeWatch('name', 'name', locals))
+ .toEqual(['name=Jim']);
+ });
+ });
+
+ describe("handle children", () => {
+ var parent, child;
+
+ beforeEach(() => {
+ var protoParent = createProtoChangeDetector();
+ parent = protoParent.instantiate(null, null);
+
+ var protoChild = createProtoChangeDetector();
+ child = protoChild.instantiate(null, null);
+ });
+
+ it("should add children", () => {
+ parent.addChild(child);
+
+ expect(parent.children.length).toEqual(1);
+ expect(parent.children[0]).toBe(child);
+ });
+
+ it("should remove children", () => {
+ parent.addChild(child);
+ parent.removeChild(child);
+
+ expect(parent.children).toEqual([]);
+ });
});
});
- }
- });
-
- describe("ContextWithVariableBindings", () => {
- it('should read a field from ContextWithVariableBindings', () => {
- var locals = new ContextWithVariableBindings(null,
- MapWrapper.createFromPairs([["key", "value"]]));
-
- expect(executeWatch('key', 'key', locals))
- .toEqual(['key=value']);
});
-
- it('should handle nested ContextWithVariableBindings', () => {
- var nested = new ContextWithVariableBindings(null,
- MapWrapper.createFromPairs([["key", "value"]]));
- var locals = new ContextWithVariableBindings(nested, MapWrapper.create());
-
- expect(executeWatch('key', 'key', locals))
- .toEqual(['key=value']);
- });
-
- it("should fall back to a regular field read when ContextWithVariableBindings " +
- "does not have the requested field", () => {
- var locals = new ContextWithVariableBindings(new Person("Jim"),
- MapWrapper.createFromPairs([["key", "value"]]));
-
- expect(executeWatch('name', 'name', locals))
- .toEqual(['name=Jim']);
- });
- });
});
}
diff --git a/modules/core/src/application.js b/modules/core/src/application.js
index 0bae1c8c24..b744fba0a8 100644
--- a/modules/core/src/application.js
+++ b/modules/core/src/application.js
@@ -4,7 +4,7 @@ import {DOM, Element} from 'facade/dom';
import {Compiler, CompilerCache} from './compiler/compiler';
import {ProtoView} from './compiler/view';
import {Reflector, reflector} from 'reflection/reflection';
-import {Parser, Lexer, ChangeDetector} from 'change_detection/change_detection';
+import {Parser, Lexer, ChangeDetection, dynamicChangeDetection, jitChangeDetection} from 'change_detection/change_detection';
import {TemplateLoader} from './compiler/template_loader';
import {DirectiveMetadataReader} from './compiler/directive_metadata_reader';
import {DirectiveMetadata} from './compiler/directive_metadata';
@@ -17,7 +17,14 @@ var _rootInjector: Injector;
// Contains everything that is safe to share between applications.
var _rootBindings = [
- bind(Reflector).toValue(reflector), Compiler, CompilerCache, TemplateLoader, DirectiveMetadataReader, Parser, Lexer
+ bind(Reflector).toValue(reflector),
+ bind(ChangeDetection).toValue(dynamicChangeDetection),
+ Compiler,
+ CompilerCache,
+ TemplateLoader,
+ DirectiveMetadataReader,
+ Parser,
+ Lexer
];
export var appViewToken = new OpaqueToken('AppView');
@@ -45,20 +52,20 @@ function _injectorBindings(appComponentType) {
return element;
}, [appComponentAnnotatedTypeToken, appDocumentToken]),
- bind(appViewToken).toAsyncFactory((compiler, injector, appElement,
+ bind(appViewToken).toAsyncFactory((changeDetection, compiler, injector, appElement,
appComponentAnnotatedType) => {
return compiler.compile(appComponentAnnotatedType.type, null).then(
(protoView) => {
- var appProtoView = ProtoView.createRootProtoView(protoView,
- appElement, appComponentAnnotatedType);
+ var appProtoView = ProtoView.createRootProtoView(protoView,
+ appElement, appComponentAnnotatedType, changeDetection.createProtoChangeDetector('root'));
// The light Dom of the app element is not considered part of
// the angular application. Thus the context and lightDomInjector are
// empty.
- var view = appProtoView.instantiate(null);
- view.hydrate(injector, null, new Object());
+ var view = appProtoView.instantiate(null);
+ view.hydrate(injector, null, new Object());
return view;
});
- }, [Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]),
+ }, [ChangeDetection, Compiler, Injector, appElementToken, appComponentAnnotatedTypeToken]),
bind(appChangeDetectorToken).toFactory((rootView) => rootView.changeDetector,
[appViewToken]),
diff --git a/modules/core/src/compiler/compiler.js b/modules/core/src/compiler/compiler.js
index c78b27abf8..56cb745d01 100644
--- a/modules/core/src/compiler/compiler.js
+++ b/modules/core/src/compiler/compiler.js
@@ -3,7 +3,7 @@ import {Promise, PromiseWrapper} from 'facade/async';
import {List, ListWrapper, MapWrapper} from 'facade/collection';
import {DOM, Element} from 'facade/dom';
-import {Parser} from 'change_detection/change_detection';
+import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {DirectiveMetadataReader} from './directive_metadata_reader';
import {ProtoView} from './view';
@@ -52,7 +52,10 @@ export class Compiler {
_reader: DirectiveMetadataReader;
_parser:Parser;
_compilerCache:CompilerCache;
- constructor(templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
+ _changeDetection:ChangeDetection;
+
+ constructor(changeDetection:ChangeDetection, templateLoader:TemplateLoader, reader: DirectiveMetadataReader, parser:Parser, cache:CompilerCache) {
+ this._changeDetection = changeDetection;
this._reader = reader;
this._parser = parser;
this._compilerCache = cache;
@@ -60,7 +63,7 @@ export class Compiler {
createSteps(component:DirectiveMetadata):List {
var dirs = ListWrapper.map(component.componentDirectives, (d) => this._reader.read(d));
- return createDefaultSteps(this._parser, component, dirs);
+ return createDefaultSteps(this._changeDetection, this._parser, component, dirs);
}
compile(component:Type, templateRoot:Element = null):Promise {
diff --git a/modules/core/src/compiler/pipeline/default_steps.js b/modules/core/src/compiler/pipeline/default_steps.js
index 012db56ae6..02b53f95b0 100644
--- a/modules/core/src/compiler/pipeline/default_steps.js
+++ b/modules/core/src/compiler/pipeline/default_steps.js
@@ -1,4 +1,4 @@
-import {Parser} from 'change_detection/change_detection';
+import {ChangeDetection, Parser} from 'change_detection/change_detection';
import {List} from 'facade/collection';
import {PropertyBindingParser} from './property_binding_parser';
@@ -17,8 +17,12 @@ import {stringify} from 'facade/lang';
* Takes in an HTMLElement and produces the ProtoViews,
* ProtoElementInjectors and ElementBinders in the end.
*/
-export function createDefaultSteps(parser:Parser, compiledComponent: DirectiveMetadata,
+export function createDefaultSteps(
+ changeDetection:ChangeDetection,
+ parser:Parser,
+ compiledComponent: DirectiveMetadata,
directives: List) {
+
var compilationUnit = stringify(compiledComponent.type);
return [
@@ -27,7 +31,7 @@ export function createDefaultSteps(parser:Parser, compiledComponent: DirectiveMe
new DirectiveParser(directives),
new TextInterpolationParser(parser, compilationUnit),
new ElementBindingMarker(),
- new ProtoViewBuilder(),
+ new ProtoViewBuilder(changeDetection),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder()
];
diff --git a/modules/core/src/compiler/pipeline/proto_view_builder.js b/modules/core/src/compiler/pipeline/proto_view_builder.js
index a557ce7008..4f5a66cdf4 100644
--- a/modules/core/src/compiler/pipeline/proto_view_builder.js
+++ b/modules/core/src/compiler/pipeline/proto_view_builder.js
@@ -2,7 +2,7 @@ import {isPresent, BaseException} from 'facade/lang';
import {ListWrapper, MapWrapper} from 'facade/collection';
import {ProtoView} from '../view';
-import {ProtoChangeDetector} from 'change_detection/change_detection';
+import {ChangeDetection} from 'change_detection/change_detection';
import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
@@ -18,10 +18,16 @@ import {CompileControl} from './compile_control';
* - CompileElement#isViewRoot
*/
export class ProtoViewBuilder extends CompileStep {
+ changeDetection:ChangeDetection;
+ constructor(changeDetection:ChangeDetection) {
+ this.changeDetection = changeDetection;
+ }
+
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null;
if (current.isViewRoot) {
- inheritedProtoView = new ProtoView(current.element, new ProtoChangeDetector());
+ var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
+ inheritedProtoView = new ProtoView(current.element, protoChangeDetector);
if (isPresent(parent)) {
if (isPresent(parent.inheritedElementBinder.nestedProtoView)) {
throw new BaseException('Only one nested view per element is allowed');
diff --git a/modules/core/src/compiler/view.js b/modules/core/src/compiler/view.js
index 44192550fa..8f1c1d7728 100644
--- a/modules/core/src/compiler/view.js
+++ b/modules/core/src/compiler/view.js
@@ -492,9 +492,12 @@ export class ProtoView {
// and the component template is already compiled into protoView.
// Used for bootstrapping.
static createRootProtoView(protoView: ProtoView,
- insertionElement, rootComponentAnnotatedType: DirectiveMetadata): ProtoView {
+ insertionElement, rootComponentAnnotatedType: DirectiveMetadata,
+ protoChangeDetector:ProtoChangeDetector
+ ): ProtoView {
+
DOM.addClass(insertionElement, 'ng-binding');
- var rootProtoView = new ProtoView(insertionElement, new ProtoChangeDetector());
+ var rootProtoView = new ProtoView(insertionElement, protoChangeDetector);
rootProtoView.instantiateInPlace = true;
var binder = rootProtoView.bindElement(
new ProtoElementInjector(null, 0, [rootComponentAnnotatedType.type], true));
diff --git a/modules/core/test/compiler/compiler_spec.js b/modules/core/test/compiler/compiler_spec.js
index b44bdd786a..a5371cda83 100644
--- a/modules/core/test/compiler/compiler_spec.js
+++ b/modules/core/test/compiler/compiler_spec.js
@@ -12,7 +12,7 @@ import {CompileElement} from 'core/compiler/pipeline/compile_element';
import {CompileStep} from 'core/compiler/pipeline/compile_step'
import {CompileControl} from 'core/compiler/pipeline/compile_control';
-import {Lexer, Parser} from 'change_detection/change_detection';
+import {Lexer, Parser, dynamicChangeDetection} from 'change_detection/change_detection';
export function main() {
describe('compiler', function() {
@@ -134,7 +134,7 @@ class RecursiveComponent {}
class TestableCompiler extends Compiler {
steps:List;
constructor(reader:DirectiveMetadataReader, steps:List) {
- super(null, reader, new Parser(new Lexer()), new CompilerCache());
+ super(dynamicChangeDetection, null, reader, new Parser(new Lexer()), new CompilerCache());
this.steps = steps;
}
createSteps(component):List {
diff --git a/modules/core/test/compiler/integration_spec.js b/modules/core/test/compiler/integration_spec.js
index 65feaf3851..ec023672d1 100644
--- a/modules/core/test/compiler/integration_spec.js
+++ b/modules/core/test/compiler/integration_spec.js
@@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
import {DOM} from 'facade/dom';
import {Injector} from 'di/di';
-import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
+import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
@@ -20,7 +20,8 @@ export function main() {
var compiler;
beforeEach( () => {
- compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
+ compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
+ new Parser(new Lexer()), new CompilerCache());
});
describe('react to record changes', function() {
diff --git a/modules/core/test/compiler/pipeline/element_binder_builder_spec.js b/modules/core/test/compiler/pipeline/element_binder_builder_spec.js
index d8a2215fb4..7473463b31 100644
--- a/modules/core/test/compiler/pipeline/element_binder_builder_spec.js
+++ b/modules/core/test/compiler/pipeline/element_binder_builder_spec.js
@@ -16,7 +16,8 @@ import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'core/
import {ProtoElementInjector} from 'core/compiler/element_injector';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
-import {ChangeDetector, Lexer, Parser, ProtoChangeDetector} from 'change_detection/change_detection';
+import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector,
+ } from 'change_detection/change_detection';
import {Injector} from 'di/di';
export function main() {
@@ -66,7 +67,7 @@ export function main() {
}
if (isPresent(current.element.getAttribute('viewroot'))) {
current.isViewRoot = true;
- current.inheritedProtoView = new ProtoView(current.element, new ProtoChangeDetector());
+ current.inheritedProtoView = new ProtoView(current.element, new DynamicProtoChangeDetector());
} else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView;
}
@@ -205,7 +206,7 @@ export function main() {
var results = pipeline.process(el(''));
var pv = results[0].inheritedProtoView;
results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
- el(''), new ProtoChangeDetector());
+ el(''), new DynamicProtoChangeDetector());
instantiateView(pv);
evalContext.prop1 = 'a';
diff --git a/modules/core/test/compiler/pipeline/proto_view_builder_spec.js b/modules/core/test/compiler/pipeline/proto_view_builder_spec.js
index 7b609dca43..d8d4e82c79 100644
--- a/modules/core/test/compiler/pipeline/proto_view_builder_spec.js
+++ b/modules/core/test/compiler/pipeline/proto_view_builder_spec.js
@@ -1,5 +1,6 @@
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'test_lib/test_lib';
import {isPresent} from 'facade/lang';
+import {dynamicChangeDetection} from 'change_detection/change_detection';
import {ElementBinder} from 'core/compiler/element_binder';
import {ProtoViewBuilder} from 'core/compiler/pipeline/proto_view_builder';
import {CompilePipeline} from 'core/compiler/pipeline/compile_pipeline';
@@ -20,7 +21,7 @@ export function main() {
current.variableBindings = MapWrapper.createFromStringMap(variableBindings);
}
current.inheritedElementBinder = new ElementBinder(null, null, null);
- }), new ProtoViewBuilder()]);
+ }), new ProtoViewBuilder(dynamicChangeDetection)]);
}
it('should not create a ProtoView when the isViewRoot flag is not set', () => {
diff --git a/modules/core/test/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js b/modules/core/test/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js
index 8f92cd9b09..ae8029e5e0 100644
--- a/modules/core/test/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js
+++ b/modules/core/test/compiler/shadow_dom/shadow_dom_emulation_integration_spec.js
@@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
import {DOM} from 'facade/dom';
import {Injector} from 'di/di';
-import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
+import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {LifeCycle} from 'core/life_cycle/life_cycle';
@@ -26,7 +26,8 @@ export function main() {
var compiler;
beforeEach( () => {
- compiler = new Compiler(null, new TestDirectiveMetadataReader(strategy),
+ compiler = new Compiler(dynamicChangeDetection, null,
+ new TestDirectiveMetadataReader(strategy),
new Parser(new Lexer()), new CompilerCache());
});
diff --git a/modules/core/test/compiler/view_spec.js b/modules/core/test/compiler/view_spec.js
index e4bb8737c3..1a4e269bc5 100644
--- a/modules/core/test/compiler/view_spec.js
+++ b/modules/core/test/compiler/view_spec.js
@@ -5,7 +5,8 @@ import {ShadowDomEmulated} from 'core/compiler/shadow_dom';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Component, Decorator, Template} from 'core/annotations/annotations';
import {OnChange} from 'core/core';
-import {Lexer, Parser, ProtoChangeDetector, ChangeDetector} from 'change_detection/change_detection';
+import {Lexer, Parser, DynamicProtoChangeDetector,
+ ChangeDetector} from 'change_detection/change_detection';
import {TemplateConfig} from 'core/annotations/template_config';
import {EventEmitter} from 'core/annotations/events';
import {List, MapWrapper} from 'facade/collection';
@@ -52,7 +53,7 @@ export function main() {
describe('instantiated from protoView', () => {
var view;
beforeEach(() => {
- var pv = new ProtoView(el(''), new ProtoChangeDetector());
+ var pv = new ProtoView(el(''), new DynamicProtoChangeDetector());
view = pv.instantiate(null);
});
@@ -73,7 +74,7 @@ export function main() {
describe('with locals', function() {
var view;
beforeEach(() => {
- var pv = new ProtoView(el(''), new ProtoChangeDetector());
+ var pv = new ProtoView(el(''), new DynamicProtoChangeDetector());
pv.bindVariable('context-foo', 'template-foo');
view = createView(pv);
});
@@ -109,7 +110,7 @@ export function main() {
}
it('should collect the root node in the ProtoView element', () => {
- var pv = new ProtoView(templateAwareCreateElement(''), new ProtoChangeDetector());
+ var pv = new ProtoView(templateAwareCreateElement(''), new DynamicProtoChangeDetector());
var view = pv.instantiate(null);
view.hydrate(null, null, null);
expect(view.nodes.length).toBe(1);
@@ -119,7 +120,7 @@ export function main() {
describe('collect elements with property bindings', () => {
it('should collect property bindings on the root element if it has the ng-binding class', () => {
- var pv = new ProtoView(templateAwareCreateElement(''), new ProtoChangeDetector());
+ var pv = new ProtoView(templateAwareCreateElement(''), new DynamicProtoChangeDetector());
pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
@@ -131,7 +132,7 @@ export function main() {
it('should collect property bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
@@ -146,7 +147,7 @@ export function main() {
describe('collect text nodes with bindings', () => {
it('should collect text nodes under the root element', () => {
- var pv = new ProtoView(templateAwareCreateElement('{{}}{{}}
'), new ProtoChangeDetector());
+ var pv = new ProtoView(templateAwareCreateElement('{{}}{{}}
'), new DynamicProtoChangeDetector());
pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('a', null));
pv.bindTextNode(2, parser.parseBinding('b', null));
@@ -160,7 +161,7 @@ export function main() {
it('should collect text nodes with bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement(' {{}}
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('b', null));
@@ -176,7 +177,7 @@ export function main() {
describe('inplace instantiation', () => {
it('should be supported.', () => {
var template = el('');
- var pv = new ProtoView(template, new ProtoChangeDetector());
+ var pv = new ProtoView(template, new DynamicProtoChangeDetector());
pv.instantiateInPlace = true;
var view = pv.instantiate(null);
view.hydrate(null, null, null);
@@ -185,7 +186,7 @@ export function main() {
it('should be off by default.', () => {
var template = el('')
- var view = new ProtoView(template, new ProtoChangeDetector())
+ var view = new ProtoView(template, new DynamicProtoChangeDetector())
.instantiate(null);
view.hydrate(null, null, null);
expect(view.nodes[0]).not.toBe(template);
@@ -202,7 +203,7 @@ export function main() {
describe('create ElementInjectors', () => {
it('should use the directives of the ProtoElementInjector', () => {
- var pv = new ProtoView(el(''), new ProtoChangeDetector());
+ var pv = new ProtoView(el(''), new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
var view = pv.instantiate(null);
@@ -213,7 +214,7 @@ export function main() {
it('should use the correct parent', () => {
var pv = new ProtoView(el('
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
@@ -227,7 +228,7 @@ export function main() {
it('should not pass the host injector when a parent injector exists', () => {
var pv = new ProtoView(el('
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent);
var testProtoElementInjector = new TestProtoElementInjector(protoParent, 1, [AnotherDirective]);
@@ -243,7 +244,7 @@ export function main() {
it('should pass the host injector when there is no parent injector', () => {
var pv = new ProtoView(el('
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]);
pv.bindElement(testProtoElementInjector);
@@ -260,7 +261,7 @@ export function main() {
it('should collect a single root element injector', () => {
var pv = new ProtoView(el('
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
@@ -273,7 +274,7 @@ export function main() {
it('should collect multiple root element injectors', () => {
var pv = new ProtoView(el('
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
@@ -290,7 +291,7 @@ export function main() {
var ctx;
function createComponentWithSubPV(subProtoView) {
- var pv = new ProtoView(el(''), new ProtoChangeDetector());
+ var pv = new ProtoView(el(''), new DynamicProtoChangeDetector());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = someComponentDirective;
binder.nestedProtoView = subProtoView;
@@ -305,7 +306,7 @@ export function main() {
}
it('should expose component services to the component', () => {
- var subpv = new ProtoView(el(''), new ProtoChangeDetector());
+ var subpv = new ProtoView(el(''), new DynamicProtoChangeDetector());
var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv);
@@ -317,7 +318,7 @@ export function main() {
it('should expose component services and component instance to directives in the shadow Dom',
() => {
var subpv = new ProtoView(
- el('hello shadow dom
'), new ProtoChangeDetector());
+ el('hello shadow dom
'), new DynamicProtoChangeDetector());
subpv.bindElement(
new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
var pv = createComponentWithSubPV(subpv);
@@ -340,7 +341,7 @@ export function main() {
it('dehydration should dehydrate child component views too', () => {
var subpv = new ProtoView(
- el('hello shadow dom
'), new ProtoChangeDetector());
+ el('hello shadow dom
'), new DynamicProtoChangeDetector());
subpv.bindElement(
new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
var pv = createComponentWithSubPV(subpv);
@@ -355,7 +356,7 @@ export function main() {
});
it('should create shadow dom', () => {
- var subpv = new ProtoView(el('hello shadow dom'), new ProtoChangeDetector());
+ var subpv = new ProtoView(el('hello shadow dom'), new DynamicProtoChangeDetector());
var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv);
@@ -364,9 +365,9 @@ export function main() {
});
it('should use the provided shadow DOM strategy', () => {
- var subpv = new ProtoView(el('hello shadow dom'), new ProtoChangeDetector());
+ var subpv = new ProtoView(el('hello shadow dom'), new DynamicProtoChangeDetector());
- var pv = new ProtoView(el(''), new ProtoChangeDetector());
+ var pv = new ProtoView(el(''), new DynamicProtoChangeDetector());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponentWithEmulatedShadowDom], true));
binder.componentDirective = new DirectiveMetadataReader().read(SomeComponentWithEmulatedShadowDom);
binder.nestedProtoView = subpv;
@@ -380,8 +381,8 @@ export function main() {
describe('with template views', () => {
function createViewWithTemplate() {
var templateProtoView = new ProtoView(
- el(''), new ProtoChangeDetector());
- var pv = new ProtoView(el(''), new ProtoChangeDetector());
+ el(''), new DynamicProtoChangeDetector());
+ var pv = new ProtoView(el(''), new DynamicProtoChangeDetector());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeTemplate]));
binder.templateDirective = someTemplateDirective;
binder.nestedProtoView = templateProtoView;
@@ -425,7 +426,7 @@ export function main() {
function createProtoView() {
var pv = new ProtoView(el(''),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null));
return pv;
@@ -460,7 +461,7 @@ export function main() {
it('should support custom event emitters', () => {
var pv = new ProtoView(el(''),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe(\$event)', null));
@@ -491,7 +492,7 @@ export function main() {
it('should consume text node changes', () => {
var pv = new ProtoView(el('{{}}
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('foo', null));
createViewAndChangeDetector(pv);
@@ -503,7 +504,7 @@ export function main() {
it('should consume element binding changes', () => {
var pv = new ProtoView(el(''),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id'));
createViewAndChangeDetector(pv);
@@ -515,7 +516,7 @@ export function main() {
it('should consume directive watch expression change', () => {
var pv = new ProtoView(el(''),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false);
createViewAndChangeDetector(pv);
@@ -527,7 +528,7 @@ export function main() {
it('should notify a directive about changes after all its properties have been set', () => {
var pv = new ProtoView(el(''),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange]));
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
@@ -544,7 +545,7 @@ export function main() {
it('should provide a map of updated properties', () => {
var pv = new ProtoView(el(''),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 0, [DirectiveImplementingOnChange]));
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
@@ -569,18 +570,20 @@ export function main() {
var element, pv;
beforeEach(() => {
element = DOM.createElement('div');
- pv = new ProtoView(el('hi
'), new ProtoChangeDetector());
+ pv = new ProtoView(el('hi
'), new DynamicProtoChangeDetector());
});
it('should create the root component when instantiated', () => {
- var rootProtoView = ProtoView.createRootProtoView(pv, element, someComponentDirective);
+ var rootProtoView = ProtoView.createRootProtoView(pv, element,
+ someComponentDirective, new DynamicProtoChangeDetector());
var view = rootProtoView.instantiate(null);
view.hydrate(new Injector([]), null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
});
it('should inject the protoView into the shadowDom', () => {
- var rootProtoView = ProtoView.createRootProtoView(pv, element, someComponentDirective);
+ var rootProtoView = ProtoView.createRootProtoView(pv, element,
+ someComponentDirective, new DynamicProtoChangeDetector());
var view = rootProtoView.instantiate(null);
view.hydrate(new Injector([]), null, null);
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');
diff --git a/modules/core/test/compiler/viewport_spec.js b/modules/core/test/compiler/viewport_spec.js
index c2411baf53..0c37dea903 100644
--- a/modules/core/test/compiler/viewport_spec.js
+++ b/modules/core/test/compiler/viewport_spec.js
@@ -6,10 +6,10 @@ import {DOM} from 'facade/dom';
import {ListWrapper, MapWrapper} from 'facade/collection';
import {Injector} from 'di/di';
import {ProtoElementInjector, ElementInjector} from 'core/compiler/element_injector';
-import {ProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'change_detection/change_detection';
+import {DynamicProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'change_detection/change_detection';
function createView(nodes) {
- var view = new View(null, nodes, new ProtoChangeDetector(), MapWrapper.create());
+ var view = new View(null, nodes, new DynamicProtoChangeDetector(), MapWrapper.create());
view.init([], [], [], [], [], [], []);
return view;
}
@@ -68,7 +68,7 @@ export function main() {
dom = el(``);
var insertionElement = dom.childNodes[1];
parentView = createView([dom.childNodes[0]]);
- protoView = new ProtoView(el('hi
'), new ProtoChangeDetector());
+ protoView = new ProtoView(el('hi
'), new DynamicProtoChangeDetector());
elementInjector = new ElementInjector(null, null, null, null);
viewPort = new ViewPort(parentView, insertionElement, protoView, elementInjector);
customViewWithOneNode = createView([el('single
')]);
@@ -165,7 +165,7 @@ export function main() {
expect(textInViewPort()).toEqual('filler one two');
expect(viewPort.length).toBe(2);
});
-
+
it('should keep views hydration state during insert', () => {
var hydratedView = new HydrateAwareFakeView(true);
var dehydratedView = new HydrateAwareFakeView(false);
@@ -212,7 +212,7 @@ export function main() {
viewPort.hydrate(new Injector([]), null);
var pv = new ProtoView(el('{{}}
'),
- new ProtoChangeDetector());
+ new DynamicProtoChangeDetector());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindTextNode(0, parser.parseBinding('foo', null));
fancyView = pv.instantiate(null);
diff --git a/modules/directives/test/ng_if_spec.js b/modules/directives/test/ng_if_spec.js
index af3f2f6132..b24ba2a8c1 100644
--- a/modules/directives/test/ng_if_spec.js
+++ b/modules/directives/test/ng_if_spec.js
@@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, IS_DARTIUM, el} f
import {DOM} from 'facade/dom';
import {Injector} from 'di/di';
-import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
+import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
@@ -17,7 +17,8 @@ export function main() {
describe('ng-if', () => {
var view, cd, compiler, component;
beforeEach(() => {
- compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
+ compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
+ new Parser(new Lexer()), new CompilerCache());
});
function createView(pv) {
diff --git a/modules/directives/test/ng_non_bindable_spec.js b/modules/directives/test/ng_non_bindable_spec.js
index e09db2d5a9..bd796459cd 100644
--- a/modules/directives/test/ng_non_bindable_spec.js
+++ b/modules/directives/test/ng_non_bindable_spec.js
@@ -1,7 +1,7 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
import {DOM} from 'facade/dom';
import {Injector} from 'di/di';
-import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
+import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Decorator, Component} from 'core/annotations/annotations';
@@ -13,7 +13,8 @@ export function main() {
describe('ng-non-bindable', () => {
var view, cd, compiler, component;
beforeEach(() => {
- compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
+ compiler = new Compiler(dynamicChangeDetection,
+ null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
});
function createView(pv) {
diff --git a/modules/directives/test/ng_repeat_spec.js b/modules/directives/test/ng_repeat_spec.js
index adb939c4e0..0e9a69509d 100644
--- a/modules/directives/test/ng_repeat_spec.js
+++ b/modules/directives/test/ng_repeat_spec.js
@@ -3,7 +3,7 @@ import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_li
import {DOM} from 'facade/dom';
import {Injector} from 'di/di';
-import {Lexer, Parser} from 'change_detection/change_detection';
+import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {OnChange} from 'core/compiler/interfaces';
@@ -20,7 +20,8 @@ export function main() {
describe('ng-repeat', () => {
var view, cd, compiler, component;
beforeEach(() => {
- compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
+ compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
+ new Parser(new Lexer()), new CompilerCache());
});
function createView(pv) {
diff --git a/modules/directives/test/ng_switch_spec.js b/modules/directives/test/ng_switch_spec.js
index f7c1fdbe71..40875cf8d1 100644
--- a/modules/directives/test/ng_switch_spec.js
+++ b/modules/directives/test/ng_switch_spec.js
@@ -1,7 +1,7 @@
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'test_lib/test_lib';
import {DOM} from 'facade/dom';
import {Injector} from 'di/di';
-import {Lexer, Parser} from 'change_detection/change_detection';
+import {Lexer, Parser, dynamicChangeDetection} from 'change_detection/change_detection';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
import {DirectiveMetadataReader} from 'core/compiler/directive_metadata_reader';
import {Component} from 'core/annotations/annotations';
@@ -12,7 +12,8 @@ export function main() {
describe('ng-switch', () => {
var view, cd, compiler, component;
beforeEach(() => {
- compiler = new Compiler(null, new DirectiveMetadataReader(), new Parser(new Lexer()), new CompilerCache());
+ compiler = new Compiler(dynamicChangeDetection, null, new DirectiveMetadataReader(),
+ new Parser(new Lexer()), new CompilerCache());
});
function createView(pv) {
diff --git a/modules/examples/src/hello_world/index_static.js b/modules/examples/src/hello_world/index_static.js
index 03d05ca166..361ad09f58 100644
--- a/modules/examples/src/hello_world/index_static.js
+++ b/modules/examples/src/hello_world/index_static.js
@@ -1,7 +1,7 @@
import * as app from './index_common';
import {Component, Decorator, TemplateConfig, NgElement} from 'angular/angular';
-import {Lexer, Parser, ChangeDetector} from 'change_detection/change_detection';
+import {Lexer, Parser, ChangeDetection, ChangeDetector} from 'change_detection/change_detection';
import {LifeCycle} from 'core/life_cycle/life_cycle';
import {Compiler, CompilerCache} from 'core/compiler/compiler';
@@ -37,8 +37,8 @@ function setup() {
});
reflector.registerType(Compiler, {
- "factory": (templateLoader, reader, parser, compilerCache) => new Compiler(templateLoader, reader, parser, compilerCache),
- "parameters": [[TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
+ "factory": (changeDetection, templateLoader, reader, parser, compilerCache) => new Compiler(changeDetection, templateLoader, reader, parser, compilerCache),
+ "parameters": [[ChangeDetection], [TemplateLoader], [DirectiveMetadataReader], [Parser], [CompilerCache]],
"annotations": []
});
diff --git a/modules/facade/src/collection.dart b/modules/facade/src/collection.dart
index 23080448ed..e3da87e3d7 100644
--- a/modules/facade/src/collection.dart
+++ b/modules/facade/src/collection.dart
@@ -89,8 +89,8 @@ class ListWrapper {
static reduce(List list, Function fn, init) {
return list.fold(init, fn);
}
- static first(List list) => list.first;
- static last(List list) => list.last;
+ static first(List list) => list.isEmpty ? null : list.first;
+ static last(List list) => list.isEmpty ? null : list.last;
static List reversed(List list) => list.reversed.toList();
static void push(List l, e) { l.add(e); }
static List concat(List a, List b) {a.addAll(b); return a;}
diff --git a/modules/facade/src/lang.dart b/modules/facade/src/lang.dart
index f754f1a687..8e524eb05a 100644
--- a/modules/facade/src/lang.dart
+++ b/modules/facade/src/lang.dart
@@ -27,6 +27,7 @@ class IMPLEMENTS {
bool isPresent(obj) => obj != null;
bool isBlank(obj) => obj == null;
+bool isString(obj) => obj is String;
String stringify(obj) => obj.toString();
diff --git a/modules/facade/src/lang.es6 b/modules/facade/src/lang.es6
index 9a6c5f3d4f..9928333943 100644
--- a/modules/facade/src/lang.es6
+++ b/modules/facade/src/lang.es6
@@ -27,6 +27,10 @@ export function isBlank(obj):boolean {
return obj === undefined || obj === null;
}
+export function isString(obj):boolean {
+ return typeof obj === "string";
+}
+
export function stringify(token):string {
if (typeof token === 'string') {
return token;