feat(change_detection): add support for pipes

This commit is contained in:
vsavkin 2015-02-12 14:56:41 -08:00
parent fa25965939
commit 695b4ebbc7
20 changed files with 436 additions and 257 deletions

View File

@ -3,7 +3,6 @@ export {Lexer} from './src/change_detection/parser/lexer';
export {Parser} from './src/change_detection/parser/parser'; export {Parser} from './src/change_detection/parser/parser';
export {ContextWithVariableBindings} export {ContextWithVariableBindings}
from './src/change_detection/parser/context_with_variable_bindings'; from './src/change_detection/parser/context_with_variable_bindings';
export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} export {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError}
from './src/change_detection/exceptions'; from './src/change_detection/exceptions';
export {ChangeRecord, ChangeDispatcher, ChangeDetector, export {ChangeRecord, ChangeDispatcher, ChangeDetector,
@ -12,9 +11,15 @@ export {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
from './src/change_detection/proto_change_detector'; from './src/change_detection/proto_change_detector';
export {DynamicChangeDetector} export {DynamicChangeDetector}
from './src/change_detection/dynamic_change_detector'; from './src/change_detection/dynamic_change_detector';
export * from './src/change_detection/pipes/pipe_registry';
export * from './src/change_detection/pipes/pipe';
import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector}
from './src/change_detection/proto_change_detector'; from './src/change_detection/proto_change_detector';
import {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
import {ArrayChanges} from './src/change_detection/pipes/array_changes';
import {NullPipe} from './src/change_detection/pipes/null_pipe';
export class ChangeDetection { export class ChangeDetection {
createProtoChangeDetector(name:string):ProtoChangeDetector{ createProtoChangeDetector(name:string):ProtoChangeDetector{
@ -23,15 +28,30 @@ export class ChangeDetection {
} }
} }
export var defaultPipes = {
"[]" : [
{
"supports" : ArrayChanges.supportsObj,
"pipe" : () => new ArrayChanges()
},
{
"supports" : NullPipe.supportsObj,
"pipe" : () => new NullPipe()
}
]
};
var _registry = new PipeRegistry(defaultPipes);
export class DynamicChangeDetection extends ChangeDetection { export class DynamicChangeDetection extends ChangeDetection {
createProtoChangeDetector(name:string):ProtoChangeDetector{ createProtoChangeDetector(name:string):ProtoChangeDetector{
return new DynamicProtoChangeDetector(); return new DynamicProtoChangeDetector(_registry);
} }
} }
export class JitChangeDetection extends ChangeDetection { export class JitChangeDetection extends ChangeDetection {
createProtoChangeDetector(name:string):ProtoChangeDetector{ createProtoChangeDetector(name:string):ProtoChangeDetector{
return new JitProtoChangeDetector(); return new JitProtoChangeDetector(_registry);
} }
} }

View File

@ -95,6 +95,7 @@ var PROTOS_ACCESSOR = "this.protos";
var CHANGE_LOCAL = "change"; var CHANGE_LOCAL = "change";
var CHANGES_LOCAL = "changes"; var CHANGES_LOCAL = "changes";
var TEMP_LOCAL = "temp"; var TEMP_LOCAL = "temp";
var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry";
function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string { function typeTemplate(type:string, cons:string, detectChanges:string, setContext:string):string {
return ` return `
@ -102,18 +103,19 @@ ${cons}
${detectChanges} ${detectChanges}
${setContext}; ${setContext};
return function(dispatcher, formatters) { return function(dispatcher, formatters, pipeRegistry) {
return new ${type}(dispatcher, formatters, protos); return new ${type}(dispatcher, formatters, pipeRegistry, protos);
} }
`; `;
} }
function constructorTemplate(type:string, fieldsDefinitions:string):string { function constructorTemplate(type:string, fieldsDefinitions:string):string {
return ` return `
var ${type} = function ${type}(dispatcher, formatters, protos) { var ${type} = function ${type}(dispatcher, formatters, pipeRegistry, protos) {
${ABSTRACT_CHANGE_DETECTOR}.call(this); ${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher; ${DISPATCHER_ACCESSOR} = dispatcher;
${FORMATTERS_ACCESSOR} = formatters; ${FORMATTERS_ACCESSOR} = formatters;
${PIPE_REGISTRY_ACCESSOR} = pipeRegistry;
${PROTOS_ACCESSOR} = protos; ${PROTOS_ACCESSOR} = protos;
${fieldsDefinitions} ${fieldsDefinitions}
} }
@ -162,14 +164,18 @@ if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
`; `;
} }
function pipeCheckTemplate(context:string, pipe:string,
function structuralCheckTemplate(selfIndex:number, field:string, context:string, notify:string):string{ value:string, change:string, addRecord:string, notify:string):string{
return ` return `
${CHANGE_LOCAL} = ${UTIL}.structuralCheck(${field}, ${context}); if (${pipe} === ${UTIL}.unitialized() || !${pipe}.supports(${context})) {
if (${CHANGE_LOCAL}) { ${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('[]', ${context});
${CHANGES_LOCAL} = ${UTIL}.addRecord(${CHANGES_LOCAL}, }
${UTIL}.changeRecord(${PROTOS_ACCESSOR}[${selfIndex}].bindingMemento, ${CHANGE_LOCAL}));
${field} = ${CHANGE_LOCAL}.currentValue; ${CHANGE_LOCAL} = ${pipe}.transform(${context});
if (! ${UTIL}.noChangeMarker(${CHANGE_LOCAL})) {
${value} = ${CHANGE_LOCAL};
${change} = true;
${addRecord}
} }
${notify} ${notify}
`; `;
@ -235,6 +241,7 @@ export class ChangeDetectorJITGenerator {
localNames:List<String>; localNames:List<String>;
changeNames:List<String>; changeNames:List<String>;
fieldNames:List<String>; fieldNames:List<String>;
pipeNames:List<String>;
constructor(typeName:string, records:List<ProtoRecord>) { constructor(typeName:string, records:List<ProtoRecord>) {
this.typeName = typeName; this.typeName = typeName;
@ -243,6 +250,7 @@ export class ChangeDetectorJITGenerator {
this.localNames = this.getLocalNames(records); this.localNames = this.getLocalNames(records);
this.changeNames = this.getChangeNames(this.localNames); this.changeNames = this.getChangeNames(this.localNames);
this.fieldNames = this.getFieldNames(this.localNames); this.fieldNames = this.getFieldNames(this.localNames);
this.pipeNames = this.getPipeNames(this.localNames);
} }
getLocalNames(records:List<ProtoRecord>):List<String> { getLocalNames(records:List<ProtoRecord>):List<String> {
@ -262,6 +270,9 @@ export class ChangeDetectorJITGenerator {
return localNames.map((n) => `this.${n}`); return localNames.map((n) => `this.${n}`);
} }
getPipeNames(localNames:List<String>):List<String> {
return localNames.map((n) => `this.${n}_pipe`);
}
generate():Function { generate():Function {
var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext()); var text = typeTemplate(this.typeName, this.genConstructor(), this.genDetectChanges(), this.genSetContext());
@ -269,7 +280,16 @@ export class ChangeDetectorJITGenerator {
} }
genConstructor():string { genConstructor():string {
return constructorTemplate(this.typeName, fieldDefinitionsTemplate(this.fieldNames)); var fields = [];
fields = fields.concat(this.fieldNames);
this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) {
fields.push(this.pipeNames[r.selfIndex]);
}
});
return constructorTemplate(this.typeName, fieldDefinitionsTemplate(fields));
} }
genSetContext():string { genSetContext():string {
@ -295,17 +315,24 @@ export class ChangeDetectorJITGenerator {
} }
genRecord(r:ProtoRecord):string { genRecord(r:ProtoRecord):string {
if (r.mode == RECORD_TYPE_STRUCTURAL_CHECK) { if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) {
return this.getStructuralCheck(r); return this.genPipeCheck (r);
} else { } else {
return this.genReferenceCheck(r); return this.genReferenceCheck(r);
} }
} }
getStructuralCheck(r:ProtoRecord):string { genPipeCheck(r:ProtoRecord):string {
var field = this.fieldNames[r.selfIndex];
var context = this.localNames[r.contextIndex]; var context = this.localNames[r.contextIndex];
return structuralCheckTemplate(r.selfIndex - 1, field, context, this.genNotify(r)); var pipe = this.pipeNames[r.selfIndex];
var newValue = this.localNames[r.selfIndex];
var oldValue = this.fieldNames[r.selfIndex];
var change = this.changeNames[r.selfIndex];
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
var notify = this.genNotify(r);
return pipeCheckTemplate(context, pipe, newValue, change, addRecord, notify);
} }
genReferenceCheck(r:ProtoRecord):string { genReferenceCheck(r:ProtoRecord):string {

View File

@ -1,10 +1,9 @@
import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang'; import {isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings'; 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 {ProtoRecord} from './proto_change_detector';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions'; import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {NO_CHANGE} from './pipes/pipe';
import {ChangeRecord, ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces'; import {ChangeRecord, ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
export var uninitialized = new Object(); export var uninitialized = new Object();
@ -85,10 +84,6 @@ function _changeRecord(bindingMemento, change) {
var _singleElementList = [null]; var _singleElementList = [null];
function _isBlank(val):boolean {
return isBlank(val) || val === uninitialized;
}
export class ChangeDetectionUtil { export class ChangeDetectionUtil {
static unitialized() { static unitialized() {
return uninitialized; return uninitialized;
@ -149,32 +144,6 @@ export class ChangeDetectionUtil {
return obj[args[0]]; return obj[args[0]];
} }
static structuralCheck(self, context) {
if (_isBlank(self) && _isBlank(context)) {
return null;
} else if (_isBlank(context)) {
return new SimpleChange(null, null);
}
if (_isBlank(self)) {
if (ArrayChanges.supports(context)) {
self = new ArrayChanges();
} else if (KeyValueChanges.supports(context)) {
self = new KeyValueChanges();
}
}
if (isBlank(self) || !self.supportsObj(context)) {
throw new BaseException(`Unsupported type (${context})`);
}
if (self.check(context)) {
return new SimpleChange(null, self); // TODO: don't wrap and return self instead
} else {
return null;
}
}
static findContext(name:string, c){ static findContext(name:string, c){
while (c instanceof ContextWithVariableBindings) { while (c instanceof ContextWithVariableBindings) {
if (c.hasBinding(name)) { if (c.hasBinding(name)) {
@ -185,6 +154,10 @@ export class ChangeDetectionUtil {
return c; return c;
} }
static noChangeMarker(value):boolean {
return value === NO_CHANGE;
}
static throwOnChange(proto:ProtoRecord, change) { static throwOnChange(proto:ProtoRecord, change) {
throw new ExpressionChangedAfterItHasBeenChecked(proto, change); throw new ExpressionChangedAfterItHasBeenChecked(proto, change);
} }

View File

@ -3,6 +3,7 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings'; import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
import {AbstractChangeDetector} from './abstract_change_detector'; import {AbstractChangeDetector} from './abstract_change_detector';
import {PipeRegistry} from './pipes/pipe_registry';
import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util'; import {ChangeDetectionUtil, SimpleChange, uninitialized} from './change_detection_util';
@ -26,16 +27,24 @@ import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './ex
export class DynamicChangeDetector extends AbstractChangeDetector { export class DynamicChangeDetector extends AbstractChangeDetector {
dispatcher:any; dispatcher:any;
formatters:Map; formatters:Map;
pipeRegistry;
values:List; values:List;
changes:List; changes:List;
pipes:List;
prevContexts:List;
protos:List<ProtoRecord>; protos:List<ProtoRecord>;
constructor(dispatcher:any, formatters:Map, protoRecords:List<ProtoRecord>) { constructor(dispatcher:any, formatters:Map, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>) {
super(); super();
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.formatters = formatters; this.formatters = formatters;
this.pipeRegistry = pipeRegistry;
this.values = ListWrapper.createFixedSize(protoRecords.length + 1); this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
this.pipes = ListWrapper.createFixedSize(protoRecords.length + 1);
this.prevContexts = ListWrapper.createFixedSize(protoRecords.length + 1);
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1); this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
this.protos = protoRecords; this.protos = protoRecords;
@ -43,6 +52,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
setContext(context:any) { setContext(context:any) {
ListWrapper.fill(this.values, uninitialized); ListWrapper.fill(this.values, uninitialized);
ListWrapper.fill(this.changes, false);
ListWrapper.fill(this.pipes, null);
ListWrapper.fill(this.prevContexts, uninitialized);
this.values[0] = context; this.values[0] = context;
} }
@ -71,7 +83,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
_check(proto:ProtoRecord) { _check(proto:ProtoRecord) {
try { try {
if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) { if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) {
return this._structuralCheck(proto); return this._pipeCheck(proto);
} else { } else {
return this._referenceCheck(proto); return this._referenceCheck(proto);
} }
@ -147,15 +159,36 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
} }
} }
_structuralCheck(proto:ProtoRecord) { _pipeCheck(proto:ProtoRecord) {
var self = this._readSelf(proto);
var context = this._readContext(proto); var context = this._readContext(proto);
var pipe = this._pipeFor(proto, context);
var change = ChangeDetectionUtil.structuralCheck(self, context); var newValue = pipe.transform(context);
if (isPresent(change)) { if (! ChangeDetectionUtil.noChangeMarker(newValue)) {
this._writeSelf(proto, change.currentValue); this._writeSelf(proto, newValue);
this._setChanged(proto, true);
if (proto.lastInBinding) {
var prevValue = this._readSelf(proto);
return ChangeDetectionUtil.simpleChange(prevValue, newValue);
} else {
return null;
}
} else {
this._setChanged(proto, false);
return null;
}
}
_pipeFor(proto:ProtoRecord, context) {
var storedPipe = this._readPipe(proto);
if (isPresent(storedPipe) && storedPipe.supports(context)) {
return storedPipe;
} else {
var pipe = this.pipeRegistry.get("[]", context);
this._writePipe(proto, pipe);
return pipe;
} }
return change;
} }
_readContext(proto:ProtoRecord) { _readContext(proto:ProtoRecord) {
@ -170,6 +203,14 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
this.values[proto.selfIndex] = value; this.values[proto.selfIndex] = value;
} }
_readPipe(proto:ProtoRecord) {
return this.pipes[proto.selfIndex];
}
_writePipe(proto:ProtoRecord, value) {
this.pipes[proto.selfIndex] = value;
}
_setChanged(proto:ProtoRecord, value:boolean) { _setChanged(proto:ProtoRecord, value:boolean) {
this.changes[proto.selfIndex] = value; this.changes[proto.selfIndex] = value;
} }

View File

@ -14,7 +14,9 @@ import {
looseIdentical, looseIdentical,
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
export class ArrayChanges { import {NO_CHANGE, Pipe} from './pipe';
export class ArrayChanges extends Pipe {
_collection; _collection;
_length:int; _length:int;
_linkedRecords:_DuplicateMap; _linkedRecords:_DuplicateMap;
@ -30,6 +32,7 @@ export class ArrayChanges {
_removalsTail:CollectionChangeRecord; _removalsTail:CollectionChangeRecord;
constructor() { constructor() {
super();
this._collection = null; this._collection = null;
this._length = null; this._length = null;
/// Keeps track of the used records at any point in time (during & across `_check()` calls) /// Keeps track of the used records at any point in time (during & across `_check()` calls)
@ -48,12 +51,12 @@ export class ArrayChanges {
this._removalsTail = null; this._removalsTail = null;
} }
static supports(obj):boolean { static supportsObj(obj):boolean {
return isListLikeIterable(obj); return isListLikeIterable(obj);
} }
supportsObj(obj):boolean { supports(obj):boolean {
return ArrayChanges.supports(obj); return ArrayChanges.supportsObj(obj);
} }
get collection() { get collection() {
@ -99,6 +102,14 @@ export class ArrayChanges {
} }
} }
transform(collection){
if (this.check(collection)) {
return this;
} else {
return NO_CHANGE;
}
}
// todo(vicb): optim for UnmodifiableListView (frozen arrays) // todo(vicb): optim for UnmodifiableListView (frozen arrays)
check(collection):boolean { check(collection):boolean {
this._reset(); this._reset();

View File

@ -0,0 +1,27 @@
import {isBlank} from 'angular2/src/facade/lang';
import {Pipe, NO_CHANGE} from './pipe';
export class NullPipe extends Pipe {
called:boolean;
constructor() {
super();
this.called = false;
}
static supportsObj(obj):boolean {
return isBlank(obj);
}
supports(obj) {
return NullPipe.supportsObj(obj);
}
transform(value) {
if (! this.called) {
this.called = true;
return null;
} else {
return NO_CHANGE;
}
}
}

View File

@ -0,0 +1,6 @@
export var NO_CHANGE = new Object();
export class Pipe {
supports(obj):boolean {return false;}
transform(value:any):any {return null;}
}

View File

@ -0,0 +1,27 @@
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
import {Pipe} from './pipe';
export class PipeRegistry {
config;
constructor(config){
this.config = config;
}
get(type:string, obj):Pipe {
var listOfConfigs = this.config[type];
if (isBlank(listOfConfigs)) {
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
}
var matchingConfig = ListWrapper.find(listOfConfigs,
(pipeConfig) => pipeConfig["supports"](obj));
if (isBlank(matchingConfig)) {
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
}
return matchingConfig["pipe"]();
}
}

View File

@ -27,6 +27,7 @@ import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util'; import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector'; import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator'; import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
import {PipeRegistry} from './pipes/pipe_registry';
import {coalesce} from './coalesce'; import {coalesce} from './coalesce';
@ -89,6 +90,7 @@ export class ProtoRecord {
} }
} }
export class ProtoChangeDetector { export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false){} addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false){}
instantiate(dispatcher:any, formatters:Map):ChangeDetector{ instantiate(dispatcher:any, formatters:Map):ChangeDetector{
@ -99,9 +101,11 @@ export class ProtoChangeDetector {
export class DynamicProtoChangeDetector extends ProtoChangeDetector { export class DynamicProtoChangeDetector extends ProtoChangeDetector {
_records:List<ProtoRecord>; _records:List<ProtoRecord>;
_recordBuilder:ProtoRecordBuilder; _recordBuilder:ProtoRecordBuilder;
_pipeRegistry:PipeRegistry;
constructor() { constructor(pipeRegistry:PipeRegistry) {
super(); super();
this._pipeRegistry = pipeRegistry;
this._records = null; this._records = null;
this._recordBuilder = new ProtoRecordBuilder(); this._recordBuilder = new ProtoRecordBuilder();
} }
@ -112,7 +116,8 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
instantiate(dispatcher:any, formatters:Map) { instantiate(dispatcher:any, formatters:Map) {
this._createRecordsIfNecessary(); this._createRecordsIfNecessary();
return new DynamicChangeDetector(dispatcher, formatters, this._records); return new DynamicChangeDetector(dispatcher, formatters,
this._pipeRegistry, this._records);
} }
_createRecordsIfNecessary() { _createRecordsIfNecessary() {
@ -127,9 +132,11 @@ var _jitProtoChangeDetectorClassCounter:number = 0;
export class JitProtoChangeDetector extends ProtoChangeDetector { export class JitProtoChangeDetector extends ProtoChangeDetector {
_factory:Function; _factory:Function;
_recordBuilder:ProtoRecordBuilder; _recordBuilder:ProtoRecordBuilder;
_pipeRegistry;
constructor() { constructor(pipeRegistry) {
super(); super();
this._pipeRegistry = pipeRegistry;
this._factory = null; this._factory = null;
this._recordBuilder = new ProtoRecordBuilder(); this._recordBuilder = new ProtoRecordBuilder();
} }
@ -140,7 +147,7 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
instantiate(dispatcher:any, formatters:Map) { instantiate(dispatcher:any, formatters:Map) {
this._createFactoryIfNecessary(); this._createFactoryIfNecessary();
return this._factory(dispatcher, formatters); return this._factory(dispatcher, formatters, this._pipeRegistry);
} }
_createFactoryIfNecessary() { _createFactoryIfNecessary() {

View File

@ -1,5 +1,4 @@
import {Viewport, onChange} from 'angular2/src/core/annotations/annotations'; import {Viewport} from 'angular2/src/core/annotations/annotations';
import {OnChange} from 'angular2/src/core/compiler/interfaces';
import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {ViewContainer} from 'angular2/src/core/compiler/view_container';
import {View} from 'angular2/src/core/compiler/view'; import {View} from 'angular2/src/core/compiler/view';
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
@ -7,21 +6,19 @@ import {ListWrapper} from 'angular2/src/facade/collection';
@Viewport({ @Viewport({
selector: '[foreach][in]', selector: '[foreach][in]',
lifecycle: [onChange],
bind: { bind: {
'in': 'iterable[]' 'in': 'iterableChanges[]'
} }
}) })
export class Foreach extends OnChange { export class Foreach {
viewContainer: ViewContainer; viewContainer: ViewContainer;
iterable;
constructor(viewContainer:ViewContainer) { constructor(viewContainer:ViewContainer) {
super(); super();
this.viewContainer = viewContainer; this.viewContainer = viewContainer;
} }
onChange(changes) {
var iteratorChanges = changes['iterable']; set iterableChanges(changes) {
if (isBlank(iteratorChanges) || isBlank(iteratorChanges.currentValue)) { if (isBlank(changes)) {
this.viewContainer.clear(); this.viewContainer.clear();
return; return;
} }
@ -29,17 +26,17 @@ export class Foreach extends OnChange {
// TODO(rado): check if change detection can produce a change record that is // TODO(rado): check if change detection can produce a change record that is
// easier to consume than current. // easier to consume than current.
var recordViewTuples = []; var recordViewTuples = [];
iteratorChanges.currentValue.forEachRemovedItem( changes.forEachRemovedItem(
(removedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(removedRecord, null)) (removedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(removedRecord, null))
); );
iteratorChanges.currentValue.forEachMovedItem( changes.forEachMovedItem(
(movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null)) (movedRecord) => ListWrapper.push(recordViewTuples, new RecordViewTuple(movedRecord, null))
); );
var insertTuples = Foreach.bulkRemove(recordViewTuples, this.viewContainer); var insertTuples = Foreach.bulkRemove(recordViewTuples, this.viewContainer);
iteratorChanges.currentValue.forEachAddedItem( changes.forEachAddedItem(
(addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null)) (addedRecord) => ListWrapper.push(insertTuples, new RecordViewTuple(addedRecord, null))
); );

View File

@ -1,5 +1,5 @@
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import {ArrayChanges} from 'angular2/src/change_detection/array_changes'; import {ArrayChanges} from 'angular2/src/change_detection/pipes/array_changes';
import {NumberWrapper} from 'angular2/src/facade/lang'; import {NumberWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
@ -23,10 +23,10 @@ export function main() {
}); });
it('should support list and iterables', () => { it('should support list and iterables', () => {
expect(ArrayChanges.supports([])).toBeTruthy(); expect(ArrayChanges.supportsObj([])).toBeTruthy();
expect(ArrayChanges.supports(new TestIterable())).toBeTruthy(); expect(ArrayChanges.supportsObj(new TestIterable())).toBeTruthy();
expect(ArrayChanges.supports(MapWrapper.create())).toBeFalsy(); expect(ArrayChanges.supportsObj(MapWrapper.create())).toBeFalsy();
expect(ArrayChanges.supports(null)).toBeFalsy(); expect(ArrayChanges.supportsObj(null)).toBeFalsy();
}); });
it('should support iterables', () => { it('should support iterables', () => {

View File

@ -8,6 +8,7 @@ import {Lexer} from 'angular2/src/change_detection/parser/lexer';
import {arrayChangesAsString, kvChangesAsString} from './util'; import {arrayChangesAsString, kvChangesAsString} from './util';
import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWithVariableBindings, import {ChangeDispatcher, DynamicChangeDetector, ChangeDetectionError, ContextWithVariableBindings,
PipeRegistry, NO_CHANGE,
CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'angular2/change_detection'; CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from 'angular2/change_detection';
@ -17,8 +18,8 @@ import {JitProtoChangeDetector, DynamicProtoChangeDetector} from 'angular2/src/c
export function main() { export function main() {
describe("change detection", () => { describe("change detection", () => {
StringMapWrapper.forEach( StringMapWrapper.forEach(
{ "dynamic": () => new DynamicProtoChangeDetector(), { "dynamic": (registry = null) => new DynamicProtoChangeDetector(registry),
"JIT": () => new JitProtoChangeDetector() "JIT": (registry = null) => new JitProtoChangeDetector(registry)
}, (createProtoChangeDetector, name) => { }, (createProtoChangeDetector, name) => {
if (name == "JIT" && IS_DARTIUM) return; if (name == "JIT" && IS_DARTIUM) return;
@ -29,8 +30,8 @@ export function main() {
} }
function createChangeDetector(memo:string, exp:string, context = null, formatters = null, function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
structural = false) { registry = null, structural = false) {
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector(registry);
pcd.addAst(ast(exp), memo, memo, structural); pcd.addAst(ast(exp), memo, memo, structural);
var dispatcher = new TestDispatcher(); var dispatcher = new TestDispatcher();
@ -41,8 +42,8 @@ export function main() {
} }
function executeWatch(memo:string, exp:string, context = null, formatters = null, function executeWatch(memo:string, exp:string, context = null, formatters = null,
content = false) { registry = null, content = false) {
var res = createChangeDetector(memo, exp, context, formatters, content); var res = createChangeDetector(memo, exp, context, formatters, registry, content);
res["changeDetector"].detectChanges(); res["changeDetector"].detectChanges();
return res["dispatcher"].log; return res["dispatcher"].log;
} }
@ -281,124 +282,6 @@ export function main() {
}); });
}); });
describe("collections", () => {
it("should not register a change when going from null to null", () => {
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([]);
});
it("should register changes when switching from null to collection and back", () => {
var context = new TestData(null);
var c = createChangeDetector('a', 'a', context, null, true);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
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", () => { describe("ContextWithVariableBindings", () => {
it('should read a field from ContextWithVariableBindings', () => { it('should read a field from ContextWithVariableBindings', () => {
var locals = new ContextWithVariableBindings(null, var locals = new ContextWithVariableBindings(null,
@ -543,8 +426,130 @@ export function main() {
expect(checkedChild.mode).toEqual(CHECK_ONCE); expect(checkedChild.mode).toEqual(CHECK_ONCE);
}); });
}); });
describe("pipes", () => {
it("should support pipes", () => {
var registry = new FakePipeRegistry(() => new CountingPipe());
var ctx = new Person("Megatron");
var c = createChangeDetector("memo", "name", ctx, null, registry, true);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.detectChanges();
expect(dispatcher.log).toEqual(['memo=Megatron state:0']);
dispatcher.clear();
cd.detectChanges();
expect(dispatcher.log).toEqual(['memo=Megatron state:1']);
});
it("should lookup pipes in the registry when the context is not supported", () => {
var registry = new FakePipeRegistry(() => new OncePipe());
var ctx = new Person("Megatron");
var c = createChangeDetector("memo", "name", ctx, null, registry, true);
var cd = c["changeDetector"];
cd.detectChanges();
expect(registry.numberOfLookups).toEqual(1);
ctx.name = "Optimus Prime";
cd.detectChanges();
expect(registry.numberOfLookups).toEqual(2);
}); });
}); });
it("should do nothing when returns NO_CHANGE", () => {
var registry = new FakePipeRegistry(() => new IdentityPipe())
var ctx = new Person("Megatron");
var c = createChangeDetector("memo", "name", ctx, null, registry, true);
var cd = c["changeDetector"];
var dispatcher = c["dispatcher"];
cd.detectChanges();
cd.detectChanges();
expect(dispatcher.log).toEqual(['memo=Megatron']);
ctx.name = "Optimus Prime";
dispatcher.clear();
cd.detectChanges();
expect(dispatcher.log).toEqual(['memo=Optimus Prime']);
});
});
});
}
class CountingPipe {
state:number;
constructor() {
this.state = 0;
}
supports(newValue) {
return true;
}
transform(value) {
return `${value} state:${this.state ++}`;
}
}
class OncePipe {
called:boolean;
constructor() {
this.called = false;;
}
supports(newValue) {
return !this.called;
}
transform(value) {
this.called = true;
return value;
}
}
class IdentityPipe {
state:any;
supports(newValue) {
return true;
}
transform(value) {
if (this.state === value) {
return NO_CHANGE;
} else {
this.state = value;
return value;
}
}
}
class FakePipeRegistry extends PipeRegistry {
numberOfLookups:number;
factory:Function;
constructor(factory) {
super({});
this.factory = factory;
this.numberOfLookups = 0;
}
get(type:string, obj) {
this.numberOfLookups ++;
return this.factory();
}
} }
class TestRecord { class TestRecord {

View File

@ -1,5 +1,5 @@
import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; import {describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import {KeyValueChanges} from 'angular2/src/change_detection/keyvalue_changes'; import {KeyValueChanges} from 'angular2/src/change_detection/pipes/keyvalue_changes';
import {NumberWrapper, isJsObject} from 'angular2/src/facade/lang'; import {NumberWrapper, isJsObject} from 'angular2/src/facade/lang';
import {MapWrapper} from 'angular2/src/facade/collection'; import {MapWrapper} from 'angular2/src/facade/collection';
import {kvChangesAsString} from './util'; import {kvChangesAsString} from './util';

View File

@ -0,0 +1,39 @@
import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib';
import {PipeRegistry} from 'angular2/src/change_detection/pipes/pipe_registry';
import {Pipe} from 'angular2/src/change_detection/pipes/pipe';
export function main() {
describe("pipe registry", () => {
var firstPipe = new Pipe();
var secondPipe = new Pipe();
it("should return the first pipe supporting the data type", () => {
var r = new PipeRegistry({
"type": [
{"supports": (obj) => false, "pipe": () => firstPipe},
{"supports": (obj) => true, "pipe": () => secondPipe}
]
});
expect(r.get("type", "some object")).toBe(secondPipe);
});
it("should throw when no matching type", () => {
var r = new PipeRegistry({});
expect(() => r.get("unknown", "some object")).toThrowError(
`Cannot find a pipe for type 'unknown' object 'some object'`
);
});
it("should throw when no matching pipe", () => {
var r = new PipeRegistry({
"type" : []
});
expect(() => r.get("type", "some object")).toThrowError(
`Cannot find a pipe for type 'type' object 'some object'`
);
});
});
}

View File

@ -71,7 +71,7 @@ export function main() {
if (isPresent(current.element.getAttribute('viewroot'))) { if (isPresent(current.element.getAttribute('viewroot'))) {
current.isViewRoot = true; current.isViewRoot = true;
current.inheritedProtoView = new ProtoView(current.element, current.inheritedProtoView = new ProtoView(current.element,
new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
} else if (isPresent(parent)) { } else if (isPresent(parent)) {
current.inheritedProtoView = parent.inheritedProtoView; current.inheritedProtoView = parent.inheritedProtoView;
} }
@ -379,7 +379,7 @@ export function main() {
var results = pipeline.process(el('<div viewroot prop-binding directives></div>')); var results = pipeline.process(el('<div viewroot prop-binding directives></div>'));
var pv = results[0].inheritedProtoView; var pv = results[0].inheritedProtoView;
results[0].inheritedElementBinder.nestedProtoView = new ProtoView( results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
el('<div></div>'), new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); el('<div></div>'), new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
instantiateView(pv); instantiateView(pv);
evalContext.prop1 = 'a'; evalContext.prop1 = 'a';

View File

@ -15,9 +15,9 @@ export function main() {
function createPipeline(selector, strategy:ShadowDomStrategy, styleHost) { function createPipeline(selector, strategy:ShadowDomStrategy, styleHost) {
var component = new Component({selector: selector}); var component = new Component({selector: selector});
var meta = new DirectiveMetadata(null, component); var meta = new DirectiveMetadata(null, component);
var transformer = new ShadowDomTransformer(meta, strategy, styleHost); var pipe = new ShadowDomTransformer(meta, strategy, styleHost);
transformer.clearCache(); pipe.clearCache();
return new CompilePipeline([transformer]); return new CompilePipeline([pipe]);
} }
it('it should set ignoreBindings to true for style elements', () => { it('it should set ignoreBindings to true for style elements', () => {

View File

@ -10,7 +10,7 @@ import {NativeShadowDomStrategy} from 'angular2/src/core/compiler/shadow_dom_str
import {DynamicProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'angular2/change_detection'; import {DynamicProtoChangeDetector, ChangeDetector, Lexer, Parser} from 'angular2/change_detection';
function createView(nodes) { function createView(nodes) {
var view = new View(null, nodes, new DynamicProtoChangeDetector(), MapWrapper.create()); var view = new View(null, nodes, new DynamicProtoChangeDetector(null), MapWrapper.create());
view.init([], [], [], [], [], [], []); view.init([], [], [], [], [], [], []);
return view; return view;
} }
@ -69,7 +69,7 @@ export function main() {
dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`); dom = el(`<div><stuff></stuff><div insert-after-me></div><stuff></stuff></div>`);
var insertionElement = dom.childNodes[1]; var insertionElement = dom.childNodes[1];
parentView = createView([dom.childNodes[0]]); parentView = createView([dom.childNodes[0]]);
protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); protoView = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
elementInjector = new ElementInjector(null, null, null, null); elementInjector = new ElementInjector(null, null, null, null);
viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector, null); viewContainer = new ViewContainer(parentView, insertionElement, protoView, elementInjector, null);
customViewWithOneNode = createView([el('<div>single</div>')]); customViewWithOneNode = createView([el('<div>single</div>')]);
@ -213,7 +213,7 @@ export function main() {
viewContainer.hydrate(new Injector([]), null); viewContainer.hydrate(new Injector([]), null);
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'), var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));
fancyView = pv.instantiate(null, null); fancyView = pv.instantiate(null, null);

View File

@ -59,7 +59,7 @@ export function main() {
describe('instantiated from protoView', () => { describe('instantiated from protoView', () => {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null); var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null), null);
view = pv.instantiate(null, null); view = pv.instantiate(null, null);
}); });
@ -77,7 +77,7 @@ export function main() {
}); });
it('should use the view pool to reuse views', () => { it('should use the view pool to reuse views', () => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null); var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null), null);
var fakeView = new FakeView(); var fakeView = new FakeView();
pv.returnToPool(fakeView); pv.returnToPool(fakeView);
@ -88,7 +88,7 @@ export function main() {
describe('with locals', function() { describe('with locals', function() {
var view; var view;
beforeEach(() => { beforeEach(() => {
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null); var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(null), null);
pv.bindVariable('context-foo', 'template-foo'); pv.bindVariable('context-foo', 'template-foo');
view = createView(pv); view = createView(pv);
}); });
@ -125,7 +125,7 @@ export function main() {
it('should collect the root node in the ProtoView element', () => { it('should collect the root node in the ProtoView element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'), var pv = new ProtoView(templateAwareCreateElement('<div id="1"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
expect(view.nodes.length).toBe(1); expect(view.nodes.length).toBe(1);
@ -136,7 +136,7 @@ export function main() {
it('should collect property bindings on the root element if it has the ng-binding class', () => { it('should collect property bindings on the root element if it has the ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'), var pv = new ProtoView(templateAwareCreateElement('<div [prop]="a" class="ng-binding"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop')); pv.bindElementProperty(parser.parseBinding('a', null), 'prop', reflector.setter('prop'));
@ -148,7 +148,7 @@ export function main() {
it('should collect property bindings on child elements with ng-binding class', () => { it('should collect property bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(templateAwareCreateElement('<div><span></span><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a')); pv.bindElementProperty(parser.parseBinding('b', null), 'a', reflector.setter('a'));
@ -164,7 +164,7 @@ export function main() {
it('should collect text nodes under the root element', () => { it('should collect text nodes under the root element', () => {
var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'), var pv = new ProtoView(templateAwareCreateElement('<div class="ng-binding">{{}}<span></span>{{}}</div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('a', null)); pv.bindTextNode(0, parser.parseBinding('a', null));
pv.bindTextNode(2, parser.parseBinding('b', null)); pv.bindTextNode(2, parser.parseBinding('b', null));
@ -178,7 +178,7 @@ export function main() {
it('should collect text nodes with bindings on child elements with ng-binding class', () => { it('should collect text nodes with bindings on child elements with ng-binding class', () => {
var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'), var pv = new ProtoView(templateAwareCreateElement('<div><span> </span><span class="ng-binding">{{}}</span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('b', null)); pv.bindTextNode(0, parser.parseBinding('b', null));
@ -194,7 +194,7 @@ export function main() {
describe('inplace instantiation', () => { describe('inplace instantiation', () => {
it('should be supported.', () => { it('should be supported.', () => {
var template = el('<div></div>'); var template = el('<div></div>');
var pv = new ProtoView(template, new DynamicProtoChangeDetector(), var pv = new ProtoView(template, new DynamicProtoChangeDetector(null),
new NativeShadowDomStrategy()); new NativeShadowDomStrategy());
pv.instantiateInPlace = true; pv.instantiateInPlace = true;
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
@ -204,7 +204,7 @@ export function main() {
it('should be off by default.', () => { it('should be off by default.', () => {
var template = el('<div></div>') var template = el('<div></div>')
var view = new ProtoView(template, new DynamicProtoChangeDetector(), var view = new ProtoView(template, new DynamicProtoChangeDetector(null),
new NativeShadowDomStrategy()) new NativeShadowDomStrategy())
.instantiate(null, null); .instantiate(null, null);
view.hydrate(null, null, null); view.hydrate(null, null, null);
@ -223,7 +223,7 @@ export function main() {
describe('create ElementInjectors', () => { describe('create ElementInjectors', () => {
it('should use the directives of the ProtoElementInjector', () => { it('should use the directives of the ProtoElementInjector', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
var view = pv.instantiate(null, null); var view = pv.instantiate(null, null);
@ -234,7 +234,7 @@ export function main() {
it('should use the correct parent', () => { it('should use the correct parent', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent); pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
@ -248,7 +248,7 @@ export function main() {
it('should not pass the host injector when a parent injector exists', () => { it('should not pass the host injector when a parent injector exists', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent); pv.bindElement(protoParent);
var testProtoElementInjector = new TestProtoElementInjector(protoParent, 1, [AnotherDirective]); var testProtoElementInjector = new TestProtoElementInjector(protoParent, 1, [AnotherDirective]);
@ -264,7 +264,7 @@ export function main() {
it('should pass the host injector when there is no parent injector', () => { it('should pass the host injector when there is no parent injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]); var testProtoElementInjector = new TestProtoElementInjector(null, 1, [AnotherDirective]);
pv.bindElement(testProtoElementInjector); pv.bindElement(testProtoElementInjector);
@ -281,7 +281,7 @@ export function main() {
it('should collect a single root element injector', () => { it('should collect a single root element injector', () => {
var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div class="ng-binding"><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]); var protoParent = new ProtoElementInjector(null, 0, [SomeDirective]);
pv.bindElement(protoParent); pv.bindElement(protoParent);
pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(protoParent, 1, [AnotherDirective]));
@ -294,7 +294,7 @@ export function main() {
it('should collect multiple root element injectors', () => { it('should collect multiple root element injectors', () => {
var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'), var pv = new ProtoView(el('<div><span class="ng-binding"></span><span class="ng-binding"></span></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 1, [SomeDirective]));
pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective])); pv.bindElement(new ProtoElementInjector(null, 2, [AnotherDirective]));
@ -312,7 +312,7 @@ export function main() {
function createComponentWithSubPV(subProtoView) { function createComponentWithSubPV(subProtoView) {
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'),
new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = someComponentDirective; binder.componentDirective = someComponentDirective;
binder.nestedProtoView = subProtoView; binder.nestedProtoView = subProtoView;
@ -327,7 +327,7 @@ export function main() {
} }
it('should expose component services to the component', () => { it('should expose component services to the component', () => {
var subpv = new ProtoView(el('<span></span>'), new DynamicProtoChangeDetector(), null); var subpv = new ProtoView(el('<span></span>'), new DynamicProtoChangeDetector(null), null);
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
var view = createNestedView(pv); var view = createNestedView(pv);
@ -340,7 +340,7 @@ export function main() {
() => { () => {
var subpv = new ProtoView( var subpv = new ProtoView(
el('<div dec class="ng-binding">hello shadow dom</div>'), el('<div dec class="ng-binding">hello shadow dom</div>'),
new DynamicProtoChangeDetector(), new DynamicProtoChangeDetector(null),
null); null);
subpv.bindElement( subpv.bindElement(
new ProtoElementInjector(null, 0, [ServiceDependentDecorator])); new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
@ -365,7 +365,7 @@ export function main() {
it('dehydration should dehydrate child component views too', () => { it('dehydration should dehydrate child component views too', () => {
var subpv = new ProtoView( var subpv = new ProtoView(
el('<div dec class="ng-binding">hello shadow dom</div>'), el('<div dec class="ng-binding">hello shadow dom</div>'),
new DynamicProtoChangeDetector(), new DynamicProtoChangeDetector(null),
null); null);
subpv.bindElement( subpv.bindElement(
new ProtoElementInjector(null, 0, [ServiceDependentDecorator])); new ProtoElementInjector(null, 0, [ServiceDependentDecorator]));
@ -382,7 +382,7 @@ export function main() {
it('should create shadow dom (Native Strategy)', () => { it('should create shadow dom (Native Strategy)', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), var subpv = new ProtoView(el('<span>hello shadow dom</span>'),
new DynamicProtoChangeDetector(), new DynamicProtoChangeDetector(null),
null); null);
var pv = createComponentWithSubPV(subpv); var pv = createComponentWithSubPV(subpv);
@ -393,10 +393,10 @@ export function main() {
it('should emulate shadow dom (Emulated Strategy)', () => { it('should emulate shadow dom (Emulated Strategy)', () => {
var subpv = new ProtoView(el('<span>hello shadow dom</span>'), var subpv = new ProtoView(el('<span>hello shadow dom</span>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'), var pv = new ProtoView(el('<cmp class="ng-binding"></cmp>'),
new DynamicProtoChangeDetector(), new EmulatedShadowDomStrategy()); new DynamicProtoChangeDetector(null), new EmulatedShadowDomStrategy());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true)); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeComponent], true));
binder.componentDirective = new DirectiveMetadataReader().read(SomeComponent); binder.componentDirective = new DirectiveMetadataReader().read(SomeComponent);
binder.nestedProtoView = subpv; binder.nestedProtoView = subpv;
@ -410,9 +410,9 @@ export function main() {
describe('with template views', () => { describe('with template views', () => {
function createViewWithViewport() { function createViewWithViewport() {
var templateProtoView = new ProtoView( var templateProtoView = new ProtoView(
el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null); el('<div id="1"></div>'), new DynamicProtoChangeDetector(null), null);
var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'), var pv = new ProtoView(el('<someTmpl class="ng-binding"></someTmpl>'),
new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeViewport])); var binder = pv.bindElement(new ProtoElementInjector(null, 0, [SomeViewport]));
binder.viewportDirective = someViewportDirective; binder.viewportDirective = someViewportDirective;
binder.nestedProtoView = templateProtoView; binder.nestedProtoView = templateProtoView;
@ -456,7 +456,7 @@ export function main() {
function createProtoView() { function createProtoView() {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new TestProtoElementInjector(null, 0, [])); pv.bindElement(new TestProtoElementInjector(null, 0, []));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
return pv; return pv;
@ -491,7 +491,7 @@ export function main() {
it('should support custom event emitters', () => { it('should support custom event emitters', () => {
var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'), var pv = new ProtoView(el('<div class="ng-binding"><div></div></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective])); pv.bindElement(new TestProtoElementInjector(null, 0, [EventEmitterDirective]));
pv.bindEvent('click', parser.parseBinding('callMe($event)', null)); pv.bindEvent('click', parser.parseBinding('callMe($event)', null));
@ -522,7 +522,7 @@ export function main() {
it('should consume text node changes', () => { it('should consume text node changes', () => {
var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'), var pv = new ProtoView(el('<div class="ng-binding">{{}}</div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(null); pv.bindElement(null);
pv.bindTextNode(0, parser.parseBinding('foo', null)); pv.bindTextNode(0, parser.parseBinding('foo', null));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
@ -534,7 +534,7 @@ export function main() {
it('should consume element binding changes', () => { it('should consume element binding changes', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(null); pv.bindElement(null);
pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id')); pv.bindElementProperty(parser.parseBinding('foo', null), 'id', reflector.setter('id'));
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
@ -546,7 +546,7 @@ export function main() {
it('should consume directive watch expression change', () => { it('should consume directive watch expression change', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective])); pv.bindElement(new ProtoElementInjector(null, 0, [SomeDirective]));
pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false); pv.bindDirectiveProperty(0, parser.parseBinding('foo', null), 'prop', reflector.setter('prop'), false);
createViewAndChangeDetector(pv); createViewAndChangeDetector(pv);
@ -558,7 +558,7 @@ export function main() {
it('should notify a directive about changes after all its properties have been set', () => { it('should notify a directive about changes after all its properties have been set', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [ pv.bindElement(new ProtoElementInjector(null, 0, [
DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]})) DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]}))
@ -577,7 +577,7 @@ export function main() {
it('should provide a map of updated properties', () => { it('should provide a map of updated properties', () => {
var pv = new ProtoView(el('<div class="ng-binding"></div>'), var pv = new ProtoView(el('<div class="ng-binding"></div>'),
new DynamicProtoChangeDetector(), null); new DynamicProtoChangeDetector(null), null);
pv.bindElement(new ProtoElementInjector(null, 0, [ pv.bindElement(new ProtoElementInjector(null, 0, [
DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]})) DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]}))
@ -604,13 +604,13 @@ export function main() {
var element, pv; var element, pv;
beforeEach(() => { beforeEach(() => {
element = DOM.createElement('div'); element = DOM.createElement('div');
pv = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(), pv = new ProtoView(el('<div>hi</div>'), new DynamicProtoChangeDetector(null),
new NativeShadowDomStrategy()); new NativeShadowDomStrategy());
}); });
it('should create the root component when instantiated', () => { it('should create the root component when instantiated', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element, var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); someComponentDirective, new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
var view = rootProtoView.instantiate(null, null); var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null); expect(view.rootElementInjectors[0].get(SomeComponent)).not.toBe(null);
@ -618,7 +618,7 @@ export function main() {
it('should inject the protoView into the shadowDom', () => { it('should inject the protoView into the shadowDom', () => {
var rootProtoView = ProtoView.createRootProtoView(pv, element, var rootProtoView = ProtoView.createRootProtoView(pv, element,
someComponentDirective, new DynamicProtoChangeDetector(), new NativeShadowDomStrategy()); someComponentDirective, new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
var view = rootProtoView.instantiate(null, null); var view = rootProtoView.instantiate(null, null);
view.hydrate(new Injector([]), null, null); view.hydrate(new Injector([]), null, null);
expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi'); expect(element.shadowRoot.childNodes[0].childNodes[0].nodeValue).toEqual('hi');

View File

@ -44,7 +44,7 @@ export function setupReflector() {
reflector.registerGetters({ reflector.registerGetters({
'scrollAreas': (o) => o.scrollAreas, 'scrollAreas': (o) => o.scrollAreas,
'length': (o) => o.length, 'length': (o) => o.length,
'iterable': (o) => o.iterable, 'iterableChanges': (o) => o.iterableChanges,
'scrollArea': (o) => o.scrollArea, 'scrollArea': (o) => o.scrollArea,
'item': (o) => o.item, 'item': (o) => o.item,
'visibleItems': (o) => o.visibleItems, 'visibleItems': (o) => o.visibleItems,
@ -95,7 +95,7 @@ export function setupReflector() {
'scrollArea': (o, v) => o.scrollArea = v, 'scrollArea': (o, v) => o.scrollArea = v,
'item': (o, v) => o.item = v, 'item': (o, v) => o.item = v,
'visibleItems': (o, v) => o.visibleItems = v, 'visibleItems': (o, v) => o.visibleItems = v,
'iterable': (o, v) => o.iterable = v, 'iterableChanges': (o, v) => o.iterableChanges = v,
'width': (o, v) => o.width = v, 'width': (o, v) => o.width = v,
'value': (o, v) => o.value = v, 'value': (o, v) => o.value = v,
'company': (o, v) => o.company = v, 'company': (o, v) => o.company = v,
@ -167,9 +167,8 @@ export function setupReflectorForAngular() {
'parameters': [[ViewContainer]], 'parameters': [[ViewContainer]],
'annotations' : [new Viewport({ 'annotations' : [new Viewport({
selector: '[foreach]', selector: '[foreach]',
lifecycle: [onChange],
bind: { bind: {
'in': 'iterable[]' 'in': 'iterableChanges[]'
} }
})] })]
}); });