feat(change_detection): change binding syntax to explicitly specify pipes
This commit is contained in:
parent
69e02ee76f
commit
58ba700b14
|
@ -18,8 +18,8 @@ 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 {PipeRegistry} from './src/change_detection/pipes/pipe_registry';
|
||||||
import {ArrayChanges} from './src/change_detection/pipes/array_changes';
|
import {ArrayChangesFactory} from './src/change_detection/pipes/array_changes';
|
||||||
import {NullPipe} from './src/change_detection/pipes/null_pipe';
|
import {NullPipeFactory} from './src/change_detection/pipes/null_pipe';
|
||||||
|
|
||||||
export class ChangeDetection {
|
export class ChangeDetection {
|
||||||
createProtoChangeDetector(name:string):ProtoChangeDetector{
|
createProtoChangeDetector(name:string):ProtoChangeDetector{
|
||||||
|
@ -29,15 +29,9 @@ export class ChangeDetection {
|
||||||
}
|
}
|
||||||
|
|
||||||
export var defaultPipes = {
|
export var defaultPipes = {
|
||||||
"[]" : [
|
"iterableDiff" : [
|
||||||
{
|
new ArrayChangesFactory(),
|
||||||
"supports" : ArrayChanges.supportsObj,
|
new NullPipeFactory()
|
||||||
"pipe" : () => new ArrayChanges()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"supports" : NullPipe.supportsObj,
|
|
||||||
"pipe" : () => new NullPipe()
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import {
|
||||||
RECORD_TYPE_PRIMITIVE_OP,
|
RECORD_TYPE_PRIMITIVE_OP,
|
||||||
RECORD_TYPE_KEYED_ACCESS,
|
RECORD_TYPE_KEYED_ACCESS,
|
||||||
RECORD_TYPE_INVOKE_FORMATTER,
|
RECORD_TYPE_INVOKE_FORMATTER,
|
||||||
RECORD_TYPE_STRUCTURAL_CHECK,
|
RECORD_TYPE_PIPE,
|
||||||
RECORD_TYPE_INTERPOLATE
|
RECORD_TYPE_INTERPOLATE
|
||||||
} from './proto_record';
|
} from './proto_record';
|
||||||
|
|
||||||
|
@ -163,11 +163,11 @@ if (${CHANGES_LOCAL} && ${CHANGES_LOCAL}.length > 0) {
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function pipeCheckTemplate(context:string, pipe:string,
|
function pipeCheckTemplate(context:string, pipe:string, pipeType:string,
|
||||||
value:string, change:string, addRecord:string, notify:string):string{
|
value:string, change:string, addRecord:string, notify:string):string{
|
||||||
return `
|
return `
|
||||||
if (${pipe} === ${UTIL}.unitialized() || !${pipe}.supports(${context})) {
|
if (${pipe} === ${UTIL}.unitialized() || !${pipe}.supports(${context})) {
|
||||||
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('[]', ${context});
|
${pipe} = ${PIPE_REGISTRY_ACCESSOR}.get('${pipeType}', ${context});
|
||||||
}
|
}
|
||||||
|
|
||||||
${CHANGE_LOCAL} = ${pipe}.transform(${context});
|
${CHANGE_LOCAL} = ${pipe}.transform(${context});
|
||||||
|
@ -283,7 +283,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
fields = fields.concat(this.fieldNames);
|
fields = fields.concat(this.fieldNames);
|
||||||
|
|
||||||
this.records.forEach((r) => {
|
this.records.forEach((r) => {
|
||||||
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) {
|
if (r.mode === RECORD_TYPE_PIPE) {
|
||||||
fields.push(this.pipeNames[r.selfIndex]);
|
fields.push(this.pipeNames[r.selfIndex]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -314,7 +314,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
genRecord(r:ProtoRecord):string {
|
genRecord(r:ProtoRecord):string {
|
||||||
if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) {
|
if (r.mode === RECORD_TYPE_PIPE) {
|
||||||
return this.genPipeCheck (r);
|
return this.genPipeCheck (r);
|
||||||
} else {
|
} else {
|
||||||
return this.genReferenceCheck(r);
|
return this.genReferenceCheck(r);
|
||||||
|
@ -331,7 +331,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
|
var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue);
|
||||||
var notify = this.genNotify(r);
|
var notify = this.genNotify(r);
|
||||||
|
|
||||||
return pipeCheckTemplate(context, pipe, newValue, change, addRecord, notify);
|
return pipeCheckTemplate(context, pipe, r.name, newValue, change, addRecord, notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
genReferenceCheck(r:ProtoRecord):string {
|
genReferenceCheck(r:ProtoRecord):string {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
RECORD_TYPE_PRIMITIVE_OP,
|
RECORD_TYPE_PRIMITIVE_OP,
|
||||||
RECORD_TYPE_KEYED_ACCESS,
|
RECORD_TYPE_KEYED_ACCESS,
|
||||||
RECORD_TYPE_INVOKE_FORMATTER,
|
RECORD_TYPE_INVOKE_FORMATTER,
|
||||||
RECORD_TYPE_STRUCTURAL_CHECK,
|
RECORD_TYPE_PIPE,
|
||||||
RECORD_TYPE_INTERPOLATE
|
RECORD_TYPE_INTERPOLATE
|
||||||
} from './proto_record';
|
} from './proto_record';
|
||||||
|
|
||||||
|
@ -81,7 +81,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_PIPE) {
|
||||||
return this._pipeCheck(proto);
|
return this._pipeCheck(proto);
|
||||||
} else {
|
} else {
|
||||||
return this._referenceCheck(proto);
|
return this._referenceCheck(proto);
|
||||||
|
@ -184,7 +184,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
if (isPresent(storedPipe) && storedPipe.supports(context)) {
|
if (isPresent(storedPipe) && storedPipe.supports(context)) {
|
||||||
return storedPipe;
|
return storedPipe;
|
||||||
} else {
|
} else {
|
||||||
var pipe = this.pipeRegistry.get("[]", context);
|
var pipe = this.pipeRegistry.get(proto.name, context);
|
||||||
this._writePipe(proto, pipe);
|
this._writePipe(proto, pipe);
|
||||||
return pipe;
|
return pipe;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,22 +33,6 @@ export class EmptyExpr extends AST {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Structural extends AST {
|
|
||||||
value:AST;
|
|
||||||
constructor(value:AST) {
|
|
||||||
super();
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
eval(context) {
|
|
||||||
return value.eval(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
visit(visitor) {
|
|
||||||
return visitor.visitStructural(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ImplicitReceiver extends AST {
|
export class ImplicitReceiver extends AST {
|
||||||
eval(context) {
|
eval(context) {
|
||||||
return context;
|
return context;
|
||||||
|
@ -204,6 +188,20 @@ export class Formatter extends AST {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Pipe extends AST {
|
||||||
|
exp:AST;
|
||||||
|
name:string;
|
||||||
|
constructor(exp:AST, name:string) {
|
||||||
|
super();
|
||||||
|
this.exp = exp;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
visit(visitor) {
|
||||||
|
return visitor.visitPipe(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class LiteralPrimitive extends AST {
|
export class LiteralPrimitive extends AST {
|
||||||
value;
|
value;
|
||||||
constructor(value) {
|
constructor(value) {
|
||||||
|
@ -460,9 +458,9 @@ export class AstVisitor {
|
||||||
visitAssignment(ast:Assignment) {}
|
visitAssignment(ast:Assignment) {}
|
||||||
visitBinary(ast:Binary) {}
|
visitBinary(ast:Binary) {}
|
||||||
visitChain(ast:Chain){}
|
visitChain(ast:Chain){}
|
||||||
visitStructural(ast:Structural) {}
|
|
||||||
visitConditional(ast:Conditional) {}
|
visitConditional(ast:Conditional) {}
|
||||||
visitFormatter(ast:Formatter) {}
|
visitFormatter(ast:Formatter) {}
|
||||||
|
visitPipe(ast:Pipe) {}
|
||||||
visitFunctionCall(ast:FunctionCall) {}
|
visitFunctionCall(ast:FunctionCall) {}
|
||||||
visitImplicitReceiver(ast:ImplicitReceiver) {}
|
visitImplicitReceiver(ast:ImplicitReceiver) {}
|
||||||
visitKeyedAccess(ast:KeyedAccess) {}
|
visitKeyedAccess(ast:KeyedAccess) {}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
PrefixNot,
|
PrefixNot,
|
||||||
Conditional,
|
Conditional,
|
||||||
Formatter,
|
Formatter,
|
||||||
|
Pipe,
|
||||||
Assignment,
|
Assignment,
|
||||||
Chain,
|
Chain,
|
||||||
KeyedAccess,
|
KeyedAccess,
|
||||||
|
@ -52,6 +53,15 @@ export class Parser {
|
||||||
return new ASTWithSource(ast, input, location);
|
return new ASTWithSource(ast, input, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addPipes(bindingAst:ASTWithSource, pipes:List<String>):ASTWithSource {
|
||||||
|
if (ListWrapper.isEmpty(pipes)) return bindingAst;
|
||||||
|
|
||||||
|
var res = ListWrapper.reduce(pipes,
|
||||||
|
(result, currentPipeName) => new Pipe(result, currentPipeName),
|
||||||
|
bindingAst.ast);
|
||||||
|
return new ASTWithSource(res, bindingAst.source, bindingAst.location);
|
||||||
|
}
|
||||||
|
|
||||||
parseTemplateBindings(input:string, location:any):List<TemplateBinding> {
|
parseTemplateBindings(input:string, location:any):List<TemplateBinding> {
|
||||||
var tokens = this._lexer.tokenize(input);
|
var tokens = this._lexer.tokenize(input);
|
||||||
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
|
return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings();
|
||||||
|
|
|
@ -16,6 +16,16 @@ import {
|
||||||
|
|
||||||
import {NO_CHANGE, Pipe} from './pipe';
|
import {NO_CHANGE, Pipe} from './pipe';
|
||||||
|
|
||||||
|
export class ArrayChangesFactory {
|
||||||
|
supports(obj):boolean {
|
||||||
|
return ArrayChanges.supportsObj(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
create():Pipe {
|
||||||
|
return new ArrayChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class ArrayChanges extends Pipe {
|
export class ArrayChanges extends Pipe {
|
||||||
_collection;
|
_collection;
|
||||||
_length:int;
|
_length:int;
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
import {isBlank} from 'angular2/src/facade/lang';
|
import {isBlank} from 'angular2/src/facade/lang';
|
||||||
import {Pipe, NO_CHANGE} from './pipe';
|
import {Pipe, NO_CHANGE} from './pipe';
|
||||||
|
|
||||||
|
export class NullPipeFactory {
|
||||||
|
supports(obj):boolean {
|
||||||
|
return NullPipe.supportsObj(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
create():Pipe {
|
||||||
|
return new NullPipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class NullPipe extends Pipe {
|
export class NullPipe extends Pipe {
|
||||||
called:boolean;
|
called:boolean;
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
|
@ -16,12 +16,12 @@ export class PipeRegistry {
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchingConfig = ListWrapper.find(listOfConfigs,
|
var matchingConfig = ListWrapper.find(listOfConfigs,
|
||||||
(pipeConfig) => pipeConfig["supports"](obj));
|
(pipeConfig) => pipeConfig.supports(obj));
|
||||||
|
|
||||||
if (isBlank(matchingConfig)) {
|
if (isBlank(matchingConfig)) {
|
||||||
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
|
throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchingConfig["pipe"]();
|
return matchingConfig.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,9 +9,9 @@ import {
|
||||||
AstVisitor,
|
AstVisitor,
|
||||||
Binary,
|
Binary,
|
||||||
Chain,
|
Chain,
|
||||||
Structural,
|
|
||||||
Conditional,
|
Conditional,
|
||||||
Formatter,
|
Formatter,
|
||||||
|
Pipe,
|
||||||
FunctionCall,
|
FunctionCall,
|
||||||
ImplicitReceiver,
|
ImplicitReceiver,
|
||||||
Interpolation,
|
Interpolation,
|
||||||
|
@ -41,12 +41,12 @@ import {
|
||||||
RECORD_TYPE_PRIMITIVE_OP,
|
RECORD_TYPE_PRIMITIVE_OP,
|
||||||
RECORD_TYPE_KEYED_ACCESS,
|
RECORD_TYPE_KEYED_ACCESS,
|
||||||
RECORD_TYPE_INVOKE_FORMATTER,
|
RECORD_TYPE_INVOKE_FORMATTER,
|
||||||
RECORD_TYPE_STRUCTURAL_CHECK,
|
RECORD_TYPE_PIPE,
|
||||||
RECORD_TYPE_INTERPOLATE
|
RECORD_TYPE_INTERPOLATE
|
||||||
} from './proto_record';
|
} from './proto_record';
|
||||||
|
|
||||||
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){}
|
||||||
instantiate(dispatcher:any, formatters:Map):ChangeDetector{
|
instantiate(dispatcher:any, formatters:Map):ChangeDetector{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -64,8 +64,8 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
||||||
this._recordBuilder = new ProtoRecordBuilder();
|
this._recordBuilder = new ProtoRecordBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) {
|
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
|
||||||
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural);
|
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate(dispatcher:any, formatters:Map) {
|
instantiate(dispatcher:any, formatters:Map) {
|
||||||
|
@ -95,8 +95,8 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
|
||||||
this._recordBuilder = new ProtoRecordBuilder();
|
this._recordBuilder = new ProtoRecordBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) {
|
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
|
||||||
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural);
|
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
|
||||||
}
|
}
|
||||||
|
|
||||||
instantiate(dispatcher:any, formatters:Map) {
|
instantiate(dispatcher:any, formatters:Map) {
|
||||||
|
@ -121,9 +121,7 @@ class ProtoRecordBuilder {
|
||||||
this.records = [];
|
this.records = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) {
|
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) {
|
||||||
if (structural) ast = new Structural(ast);
|
|
||||||
|
|
||||||
var last = ListWrapper.last(this.records);
|
var last = ListWrapper.last(this.records);
|
||||||
if (isPresent(last) && last.directiveMemento == directiveMemento) {
|
if (isPresent(last) && last.directiveMemento == directiveMemento) {
|
||||||
last.lastInDirective = false;
|
last.lastInDirective = false;
|
||||||
|
@ -228,9 +226,9 @@ class _ConvertAstIntoProtoRecords {
|
||||||
ChangeDetectionUtil.cond, [c,t,f], null, 0);
|
ChangeDetectionUtil.cond, [c,t,f], null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitStructural(ast:Structural) {
|
visitPipe(ast:Pipe) {
|
||||||
var value = ast.value.visit(this);
|
var value = ast.exp.visit(this);
|
||||||
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
|
return this._addRecord(RECORD_TYPE_PIPE, ast.name, ast.name, [], null, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
visitKeyedAccess(ast:KeyedAccess) {
|
visitKeyedAccess(ast:KeyedAccess) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const RECORD_TYPE_INVOKE_METHOD = 4;
|
||||||
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
|
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
|
||||||
export const RECORD_TYPE_KEYED_ACCESS = 6;
|
export const RECORD_TYPE_KEYED_ACCESS = 6;
|
||||||
export const RECORD_TYPE_INVOKE_FORMATTER = 7;
|
export const RECORD_TYPE_INVOKE_FORMATTER = 7;
|
||||||
export const RECORD_TYPE_STRUCTURAL_CHECK = 8;
|
export const RECORD_TYPE_PIPE = 8;
|
||||||
export const RECORD_TYPE_INTERPOLATE = 9;
|
export const RECORD_TYPE_INTERPOLATE = 9;
|
||||||
|
|
||||||
export class ProtoRecord {
|
export class ProtoRecord {
|
||||||
|
|
|
@ -195,34 +195,38 @@ export class ElementBinderBuilder extends CompileStep {
|
||||||
var directive = ListWrapper.get(directives, directiveIndex);
|
var directive = ListWrapper.get(directives, directiveIndex);
|
||||||
var annotation = directive.annotation;
|
var annotation = directive.annotation;
|
||||||
if (isBlank(annotation.bind)) continue;
|
if (isBlank(annotation.bind)) continue;
|
||||||
var _this = this;
|
StringMapWrapper.forEach(annotation.bind, (bindConfig, dirProp) => {
|
||||||
StringMapWrapper.forEach(annotation.bind, function (elProp, dirProp) {
|
var bindConfigParts = this._splitBindConfig(bindConfig);
|
||||||
var expression = isPresent(compileElement.propertyBindings) ?
|
var elProp = bindConfigParts[0];
|
||||||
|
var pipes = ListWrapper.slice(bindConfigParts, 1, bindConfigParts.length);
|
||||||
|
|
||||||
|
var bindingAst = isPresent(compileElement.propertyBindings) ?
|
||||||
MapWrapper.get(compileElement.propertyBindings, elProp) :
|
MapWrapper.get(compileElement.propertyBindings, elProp) :
|
||||||
null;
|
null;
|
||||||
if (isBlank(expression)) {
|
|
||||||
|
if (isBlank(bindingAst)) {
|
||||||
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
|
var attributeValue = MapWrapper.get(compileElement.attrs(), elProp);
|
||||||
if (isPresent(attributeValue)) {
|
if (isPresent(attributeValue)) {
|
||||||
expression = _this._parser.wrapLiteralPrimitive(attributeValue, _this._compilationUnit);
|
bindingAst = this._parser.wrapLiteralPrimitive(attributeValue, this._compilationUnit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
// Bindings are optional, so this binding only needs to be set up if an expression is given.
|
||||||
if (isPresent(expression)) {
|
if (isPresent(bindingAst)) {
|
||||||
var len = dirProp.length;
|
var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes);
|
||||||
var dirBindingName = dirProp;
|
|
||||||
var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']';
|
|
||||||
if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2);
|
|
||||||
|
|
||||||
protoView.bindDirectiveProperty(
|
protoView.bindDirectiveProperty(
|
||||||
directiveIndex,
|
directiveIndex,
|
||||||
expression,
|
fullExpAstWithBindPipes,
|
||||||
dirBindingName,
|
dirProp,
|
||||||
reflector.setter(dirBindingName),
|
reflector.setter(dirProp)
|
||||||
isContentWatch
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
_splitBindConfig(bindConfig:string) {
|
||||||
|
var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|"));
|
||||||
|
return ListWrapper.map(parts, (s) => s.trim());
|
||||||
|
}
|
||||||
|
}
|
|
@ -511,8 +511,7 @@ export class ProtoView {
|
||||||
directiveIndex:number,
|
directiveIndex:number,
|
||||||
expression:AST,
|
expression:AST,
|
||||||
setterName:string,
|
setterName:string,
|
||||||
setter:SetterFn,
|
setter:SetterFn) {
|
||||||
isContentWatch: boolean) {
|
|
||||||
|
|
||||||
var bindingMemento = new DirectiveBindingMemento(
|
var bindingMemento = new DirectiveBindingMemento(
|
||||||
this.elementBinders.length-1,
|
this.elementBinders.length-1,
|
||||||
|
@ -521,7 +520,7 @@ export class ProtoView {
|
||||||
setter
|
setter
|
||||||
);
|
);
|
||||||
var directiveMemento = DirectiveMemento.get(bindingMemento);
|
var directiveMemento = DirectiveMemento.get(bindingMemento);
|
||||||
this.protoChangeDetector.addAst(expression, bindingMemento, directiveMemento, isContentWatch);
|
this.protoChangeDetector.addAst(expression, bindingMemento, directiveMemento);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
|
// Create a rootView as if the compiler encountered <rootcmp></rootcmp>,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {ListWrapper} from 'angular2/src/facade/collection';
|
||||||
@Viewport({
|
@Viewport({
|
||||||
selector: '[foreach][in]',
|
selector: '[foreach][in]',
|
||||||
bind: {
|
bind: {
|
||||||
'iterableChanges[]': 'in'
|
'iterableChanges': 'in | iterableDiff'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
export class Foreach {
|
export class Foreach {
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach, IS_DAR
|
||||||
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} from 'angular2/src/facade/lang';
|
import {isPresent, isBlank, isJsObject, BaseException, FunctionWrapper} 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 {Pipe} from 'angular2/src/change_detection/parser/ast';
|
||||||
import {Parser} from 'angular2/src/change_detection/parser/parser';
|
import {Parser} from 'angular2/src/change_detection/parser/parser';
|
||||||
import {Lexer} from 'angular2/src/change_detection/parser/lexer';
|
import {Lexer} from 'angular2/src/change_detection/parser/lexer';
|
||||||
|
|
||||||
|
@ -29,9 +30,13 @@ export function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
|
function createChangeDetector(memo:string, exp:string, context = null, formatters = null,
|
||||||
registry = null, structural = false) {
|
registry = null, pipeType:string = null) {
|
||||||
var pcd = createProtoChangeDetector(registry);
|
var pcd = createProtoChangeDetector(registry);
|
||||||
pcd.addAst(ast(exp), memo, memo, structural);
|
var parsedAst = ast(exp);
|
||||||
|
if (isPresent(pipeType)) {
|
||||||
|
parsedAst = new Pipe(parsedAst, pipeType);
|
||||||
|
}
|
||||||
|
pcd.addAst(parsedAst, memo, memo);
|
||||||
|
|
||||||
var dispatcher = new TestDispatcher();
|
var dispatcher = new TestDispatcher();
|
||||||
var cd = pcd.instantiate(dispatcher, formatters);
|
var cd = pcd.instantiate(dispatcher, formatters);
|
||||||
|
@ -40,9 +45,8 @@ export function main() {
|
||||||
return {"changeDetector" : cd, "dispatcher" : dispatcher};
|
return {"changeDetector" : cd, "dispatcher" : dispatcher};
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeWatch(memo:string, exp:string, context = null, formatters = null,
|
function executeWatch(memo:string, exp:string, context = null, formatters = null) {
|
||||||
registry = null, content = false) {
|
var res = createChangeDetector(memo, exp, context, formatters);
|
||||||
var res = createChangeDetector(memo, exp, context, formatters, registry, content);
|
|
||||||
res["changeDetector"].detectChanges();
|
res["changeDetector"].detectChanges();
|
||||||
return res["dispatcher"].log;
|
return res["dispatcher"].log;
|
||||||
}
|
}
|
||||||
|
@ -190,7 +194,7 @@ export function main() {
|
||||||
var parser = new Parser(new Lexer());
|
var parser = new Parser(new Lexer());
|
||||||
var pcd = createProtoChangeDetector();
|
var pcd = createProtoChangeDetector();
|
||||||
var ast = parser.parseInterpolation("B{{a}}A", "location");
|
var ast = parser.parseInterpolation("B{{a}}A", "location");
|
||||||
pcd.addAst(ast, "memo", "memo", false);
|
pcd.addAst(ast, "memo", "memo");
|
||||||
|
|
||||||
var dispatcher = new TestDispatcher();
|
var dispatcher = new TestDispatcher();
|
||||||
var cd = pcd.instantiate(dispatcher, MapWrapper.create());
|
var cd = pcd.instantiate(dispatcher, MapWrapper.create());
|
||||||
|
@ -428,10 +432,10 @@ export function main() {
|
||||||
|
|
||||||
describe("pipes", () => {
|
describe("pipes", () => {
|
||||||
it("should support pipes", () => {
|
it("should support pipes", () => {
|
||||||
var registry = new FakePipeRegistry(() => new CountingPipe());
|
var registry = new FakePipeRegistry('pipe', () => new CountingPipe());
|
||||||
var ctx = new Person("Megatron");
|
var ctx = new Person("Megatron");
|
||||||
|
|
||||||
var c = createChangeDetector("memo", "name", ctx, null, registry, true);
|
var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
|
||||||
var cd = c["changeDetector"];
|
var cd = c["changeDetector"];
|
||||||
var dispatcher = c["dispatcher"];
|
var dispatcher = c["dispatcher"];
|
||||||
|
|
||||||
|
@ -446,10 +450,10 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should lookup pipes in the registry when the context is not supported", () => {
|
it("should lookup pipes in the registry when the context is not supported", () => {
|
||||||
var registry = new FakePipeRegistry(() => new OncePipe());
|
var registry = new FakePipeRegistry('pipe', () => new OncePipe());
|
||||||
var ctx = new Person("Megatron");
|
var ctx = new Person("Megatron");
|
||||||
|
|
||||||
var c = createChangeDetector("memo", "name", ctx, null, registry, true);
|
var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
|
||||||
var cd = c["changeDetector"];
|
var cd = c["changeDetector"];
|
||||||
|
|
||||||
cd.detectChanges();
|
cd.detectChanges();
|
||||||
|
@ -464,10 +468,10 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should do nothing when returns NO_CHANGE", () => {
|
it("should do nothing when returns NO_CHANGE", () => {
|
||||||
var registry = new FakePipeRegistry(() => new IdentityPipe())
|
var registry = new FakePipeRegistry('pipe', () => new IdentityPipe())
|
||||||
var ctx = new Person("Megatron");
|
var ctx = new Person("Megatron");
|
||||||
|
|
||||||
var c = createChangeDetector("memo", "name", ctx, null, registry, true);
|
var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe');
|
||||||
var cd = c["changeDetector"];
|
var cd = c["changeDetector"];
|
||||||
var dispatcher = c["dispatcher"];
|
var dispatcher = c["dispatcher"];
|
||||||
|
|
||||||
|
@ -537,15 +541,18 @@ class IdentityPipe {
|
||||||
|
|
||||||
class FakePipeRegistry extends PipeRegistry {
|
class FakePipeRegistry extends PipeRegistry {
|
||||||
numberOfLookups:number;
|
numberOfLookups:number;
|
||||||
|
pipeType:string;
|
||||||
factory:Function;
|
factory:Function;
|
||||||
|
|
||||||
constructor(factory) {
|
constructor(pipeType, factory) {
|
||||||
super({});
|
super({});
|
||||||
|
this.pipeType = pipeType;
|
||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
this.numberOfLookups = 0;
|
this.numberOfLookups = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get(type:string, obj) {
|
get(type:string, obj) {
|
||||||
|
if (type != this.pipeType) return null;
|
||||||
this.numberOfLookups ++;
|
this.numberOfLookups ++;
|
||||||
return this.factory();
|
return this.factory();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,10 @@ export function main() {
|
||||||
return createParser().parseInterpolation(text, location);
|
return createParser().parseInterpolation(text, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addPipes(ast, pipes) {
|
||||||
|
return createParser().addPipes(ast, pipes);
|
||||||
|
}
|
||||||
|
|
||||||
function expectEval(text, passedInContext = null) {
|
function expectEval(text, passedInContext = null) {
|
||||||
var c = isBlank(passedInContext) ? td() : passedInContext;
|
var c = isBlank(passedInContext) ? td() : passedInContext;
|
||||||
return expect(parseAction(text).eval(c));
|
return expect(parseAction(text).eval(c));
|
||||||
|
@ -544,6 +548,29 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('addPipes', () => {
|
||||||
|
it('should return the given ast whe the list of pipes is empty', () => {
|
||||||
|
var ast = parseBinding("1 + 1", "Location");
|
||||||
|
var transformedAst = addPipes(ast, []);
|
||||||
|
expect(transformedAst).toBe(ast);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should append pipe ast nodes', () => {
|
||||||
|
var ast = parseBinding("1 + 1", "Location");
|
||||||
|
var transformedAst = addPipes(ast, ['one', 'two']);
|
||||||
|
expect(transformedAst.ast.name).toEqual("two");
|
||||||
|
expect(transformedAst.ast.exp.name).toEqual("one");
|
||||||
|
expect(transformedAst.ast.exp.exp.operation).toEqual("+");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should preserve location and source', () => {
|
||||||
|
var ast = parseBinding("1 + 1", "Location");
|
||||||
|
var transformedAst = addPipes(ast, ['one', 'two']);
|
||||||
|
expect(transformedAst.source).toEqual("1 + 1");
|
||||||
|
expect(transformedAst.location).toEqual("Location");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('wrapLiteralPrimitive', () => {
|
describe('wrapLiteralPrimitive', () => {
|
||||||
it('should wrap a literal primitive', () => {
|
it('should wrap a literal primitive', () => {
|
||||||
expect(createParser().wrapLiteralPrimitive("foo", null).eval(null)).toEqual("foo");
|
expect(createParser().wrapLiteralPrimitive("foo", null).eval(null)).toEqual("foo");
|
||||||
|
|
|
@ -11,8 +11,8 @@ export function main() {
|
||||||
it("should return the first pipe supporting the data type", () => {
|
it("should return the first pipe supporting the data type", () => {
|
||||||
var r = new PipeRegistry({
|
var r = new PipeRegistry({
|
||||||
"type": [
|
"type": [
|
||||||
{"supports": (obj) => false, "pipe": () => firstPipe},
|
new PipeFactory(false, firstPipe),
|
||||||
{"supports": (obj) => true, "pipe": () => secondPipe}
|
new PipeFactory(true, secondPipe)
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -37,3 +37,21 @@ export function main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PipeFactory {
|
||||||
|
shouldSupport:boolean;
|
||||||
|
pipe:any;
|
||||||
|
|
||||||
|
constructor(shouldSupport:boolean, pipe:any) {
|
||||||
|
this.shouldSupport = shouldSupport;
|
||||||
|
this.pipe = pipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
supports(obj):boolean {
|
||||||
|
return this.shouldSupport;
|
||||||
|
}
|
||||||
|
|
||||||
|
create():Pipe {
|
||||||
|
return this.pipe;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
|
import {describe, beforeEach, it, expect, iit, ddescribe, el} from 'angular2/test_lib';
|
||||||
import {isPresent} from 'angular2/src/facade/lang';
|
import {isPresent, normalizeBlank} from 'angular2/src/facade/lang';
|
||||||
import {DOM} from 'angular2/src/facade/dom';
|
import {DOM} from 'angular2/src/facade/dom';
|
||||||
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
import {ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import {ProtoView, ElementPropertyMemento, DirectivePropertyMemento} from 'angul
|
||||||
import {ProtoElementInjector} from 'angular2/src/core/compiler/element_injector';
|
import {ProtoElementInjector} from 'angular2/src/core/compiler/element_injector';
|
||||||
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
|
||||||
|
|
||||||
import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector,
|
import {ChangeDetector, Lexer, Parser, DynamicProtoChangeDetector, PipeRegistry, Pipe
|
||||||
} from 'angular2/change_detection';
|
} from 'angular2/change_detection';
|
||||||
import {Injector} from 'angular2/di';
|
import {Injector} from 'angular2/di';
|
||||||
|
|
||||||
|
@ -23,8 +23,7 @@ export function main() {
|
||||||
describe('ElementBinderBuilder', () => {
|
describe('ElementBinderBuilder', () => {
|
||||||
var evalContext, view, changeDetector;
|
var evalContext, view, changeDetector;
|
||||||
|
|
||||||
function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector
|
function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector, registry}={}) {
|
||||||
}={}) {
|
|
||||||
var reflector = new DirectiveMetadataReader();
|
var reflector = new DirectiveMetadataReader();
|
||||||
var parser = new Parser(new Lexer());
|
var parser = new Parser(new Lexer());
|
||||||
return new CompilePipeline([
|
return new CompilePipeline([
|
||||||
|
@ -70,8 +69,10 @@ 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(
|
||||||
new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy());
|
current.element,
|
||||||
|
new DynamicProtoChangeDetector(normalizeBlank(registry)),
|
||||||
|
new NativeShadowDomStrategy());
|
||||||
} else if (isPresent(parent)) {
|
} else if (isPresent(parent)) {
|
||||||
current.inheritedProtoView = parent.inheritedProtoView;
|
current.inheritedProtoView = parent.inheritedProtoView;
|
||||||
}
|
}
|
||||||
|
@ -393,6 +394,37 @@ export function main() {
|
||||||
expect(view.elementInjectors[0].get(SomeComponentDirectiveWithBinding).compProp).toBe('c');
|
expect(view.elementInjectors[0].get(SomeComponentDirectiveWithBinding).compProp).toBe('c');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should bind directive properties with pipes', () => {
|
||||||
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
|
'boundprop': 'prop1'
|
||||||
|
});
|
||||||
|
|
||||||
|
var directives = [DirectiveWithBindingsThatHavePipes];
|
||||||
|
var protoElementInjector = new ProtoElementInjector(null, 0, directives, true);
|
||||||
|
|
||||||
|
var registry = new PipeRegistry({
|
||||||
|
"double" : [new DoublePipeFactory()]
|
||||||
|
});
|
||||||
|
|
||||||
|
var pipeline = createPipeline({
|
||||||
|
propertyBindings: propertyBindings,
|
||||||
|
directives: directives,
|
||||||
|
protoElementInjector: protoElementInjector,
|
||||||
|
registry: registry
|
||||||
|
});
|
||||||
|
|
||||||
|
var results = pipeline.process(el('<div viewroot prop-binding directives></div>'));
|
||||||
|
var pv = results[0].inheritedProtoView;
|
||||||
|
results[0].inheritedElementBinder.nestedProtoView = new ProtoView(
|
||||||
|
el('<div></div>'), new DynamicProtoChangeDetector(registry), new NativeShadowDomStrategy());
|
||||||
|
|
||||||
|
instantiateView(pv);
|
||||||
|
evalContext.prop1 = 'a';
|
||||||
|
changeDetector.detectChanges();
|
||||||
|
|
||||||
|
expect(view.elementInjectors[0].get(DirectiveWithBindingsThatHavePipes).compProp).toEqual('aa');
|
||||||
|
});
|
||||||
|
|
||||||
it('should bind directive properties for sibling elements', () => {
|
it('should bind directive properties for sibling elements', () => {
|
||||||
var propertyBindings = MapWrapper.createFromStringMap({
|
var propertyBindings = MapWrapper.createFromStringMap({
|
||||||
'boundprop1': 'prop1'
|
'boundprop1': 'prop1'
|
||||||
|
@ -504,6 +536,34 @@ class SomeComponentDirectiveWithBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({bind: {'compProp':'boundprop | double'}})
|
||||||
|
class DirectiveWithBindingsThatHavePipes {
|
||||||
|
compProp;
|
||||||
|
constructor() {
|
||||||
|
this.compProp = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoublePipe extends Pipe {
|
||||||
|
supports(obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
transform(value) {
|
||||||
|
return `${value}${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DoublePipeFactory {
|
||||||
|
supports(obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
create() {
|
||||||
|
return new DoublePipe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Context {
|
class Context {
|
||||||
prop1;
|
prop1;
|
||||||
prop2;
|
prop2;
|
||||||
|
|
|
@ -548,7 +548,7 @@ export function main() {
|
||||||
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
var pv = new ProtoView(el('<div class="ng-binding"></div>'),
|
||||||
new DynamicProtoChangeDetector(null), 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'));
|
||||||
createViewAndChangeDetector(pv);
|
createViewAndChangeDetector(pv);
|
||||||
|
|
||||||
ctx.foo = 'buz';
|
ctx.foo = 'buz';
|
||||||
|
@ -563,8 +563,8 @@ export function main() {
|
||||||
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]}))
|
||||||
]));
|
]));
|
||||||
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
|
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'));
|
||||||
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false);
|
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'));
|
||||||
createViewAndChangeDetector(pv);
|
createViewAndChangeDetector(pv);
|
||||||
|
|
||||||
ctx.a = 100;
|
ctx.a = 100;
|
||||||
|
@ -582,8 +582,8 @@ export function main() {
|
||||||
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]}))
|
||||||
]));
|
]));
|
||||||
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false);
|
pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'));
|
||||||
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false);
|
pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'));
|
||||||
createViewAndChangeDetector(pv);
|
createViewAndChangeDetector(pv);
|
||||||
|
|
||||||
ctx.a = 0;
|
ctx.a = 0;
|
||||||
|
|
|
@ -119,7 +119,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) {
|
||||||
parser.parseBinding('field9', null)
|
parser.parseBinding('field9', null)
|
||||||
];
|
];
|
||||||
for (var j = 0; j < 10; ++j) {
|
for (var j = 0; j < 10; ++j) {
|
||||||
proto.addAst(astWithSource[j].ast, "memo", j, false);
|
proto.addAst(astWithSource[j].ast, "memo", j);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < iterations; ++i) {
|
for (var i = 0; i < iterations; ++i) {
|
||||||
|
|
|
@ -169,7 +169,7 @@ export function setupReflectorForAngular() {
|
||||||
'annotations' : [new Viewport({
|
'annotations' : [new Viewport({
|
||||||
selector: '[foreach]',
|
selector: '[foreach]',
|
||||||
bind: {
|
bind: {
|
||||||
'iterableChanges[]': 'in'
|
'iterableChanges': 'in | iterableDiff'
|
||||||
}
|
}
|
||||||
})]
|
})]
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue