diff --git a/modules/angular2/change_detection.js b/modules/angular2/change_detection.js index aa5b8f5163..26c7f4bee0 100644 --- a/modules/angular2/change_detection.js +++ b/modules/angular2/change_detection.js @@ -18,8 +18,8 @@ export * from './src/change_detection/pipes/pipe'; import {ProtoChangeDetector, DynamicProtoChangeDetector, JitProtoChangeDetector} 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'; +import {ArrayChangesFactory} from './src/change_detection/pipes/array_changes'; +import {NullPipeFactory} from './src/change_detection/pipes/null_pipe'; export class ChangeDetection { createProtoChangeDetector(name:string):ProtoChangeDetector{ @@ -29,15 +29,9 @@ export class ChangeDetection { } export var defaultPipes = { - "[]" : [ - { - "supports" : ArrayChanges.supportsObj, - "pipe" : () => new ArrayChanges() - }, - { - "supports" : NullPipe.supportsObj, - "pipe" : () => new NullPipe() - } + "iterableDiff" : [ + new ArrayChangesFactory(), + new NullPipeFactory() ] }; diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 index 885ad8a5af..80d025d9b8 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 @@ -15,7 +15,7 @@ import { RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_INVOKE_FORMATTER, - RECORD_TYPE_STRUCTURAL_CHECK, + RECORD_TYPE_PIPE, RECORD_TYPE_INTERPOLATE } 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{ return ` 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}); @@ -283,7 +283,7 @@ export class ChangeDetectorJITGenerator { fields = fields.concat(this.fieldNames); this.records.forEach((r) => { - if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) { + if (r.mode === RECORD_TYPE_PIPE) { fields.push(this.pipeNames[r.selfIndex]); } }); @@ -314,7 +314,7 @@ export class ChangeDetectorJITGenerator { } genRecord(r:ProtoRecord):string { - if (r.mode === RECORD_TYPE_STRUCTURAL_CHECK) { + if (r.mode === RECORD_TYPE_PIPE) { return this.genPipeCheck (r); } else { return this.genReferenceCheck(r); @@ -331,7 +331,7 @@ export class ChangeDetectorJITGenerator { var addRecord = addSimpleChangeRecordTemplate(r.selfIndex - 1, oldValue, newValue); 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 { diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.js b/modules/angular2/src/change_detection/dynamic_change_detector.js index 63fd06bd7b..3d6cdffd3a 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.js +++ b/modules/angular2/src/change_detection/dynamic_change_detector.js @@ -17,7 +17,7 @@ import { RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_INVOKE_FORMATTER, - RECORD_TYPE_STRUCTURAL_CHECK, + RECORD_TYPE_PIPE, RECORD_TYPE_INTERPOLATE } from './proto_record'; @@ -81,7 +81,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { _check(proto:ProtoRecord) { try { - if (proto.mode == RECORD_TYPE_STRUCTURAL_CHECK) { + if (proto.mode == RECORD_TYPE_PIPE) { return this._pipeCheck(proto); } else { return this._referenceCheck(proto); @@ -184,7 +184,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { if (isPresent(storedPipe) && storedPipe.supports(context)) { return storedPipe; } else { - var pipe = this.pipeRegistry.get("[]", context); + var pipe = this.pipeRegistry.get(proto.name, context); this._writePipe(proto, pipe); return pipe; } diff --git a/modules/angular2/src/change_detection/parser/ast.js b/modules/angular2/src/change_detection/parser/ast.js index 787524c2e9..4ca3858c00 100644 --- a/modules/angular2/src/change_detection/parser/ast.js +++ b/modules/angular2/src/change_detection/parser/ast.js @@ -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 { eval(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 { value; constructor(value) { @@ -460,9 +458,9 @@ export class AstVisitor { visitAssignment(ast:Assignment) {} visitBinary(ast:Binary) {} visitChain(ast:Chain){} - visitStructural(ast:Structural) {} visitConditional(ast:Conditional) {} visitFormatter(ast:Formatter) {} + visitPipe(ast:Pipe) {} visitFunctionCall(ast:FunctionCall) {} visitImplicitReceiver(ast:ImplicitReceiver) {} visitKeyedAccess(ast:KeyedAccess) {} diff --git a/modules/angular2/src/change_detection/parser/parser.js b/modules/angular2/src/change_detection/parser/parser.js index a1e5e804d5..402e5f9e92 100644 --- a/modules/angular2/src/change_detection/parser/parser.js +++ b/modules/angular2/src/change_detection/parser/parser.js @@ -14,6 +14,7 @@ import { PrefixNot, Conditional, Formatter, + Pipe, Assignment, Chain, KeyedAccess, @@ -52,6 +53,15 @@ export class Parser { return new ASTWithSource(ast, input, location); } + addPipes(bindingAst:ASTWithSource, pipes:List):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 { var tokens = this._lexer.tokenize(input); return new _ParseAST(input, location, tokens, this._reflector, false).parseTemplateBindings(); diff --git a/modules/angular2/src/change_detection/pipes/array_changes.js b/modules/angular2/src/change_detection/pipes/array_changes.js index 7437563ecf..10fa537553 100644 --- a/modules/angular2/src/change_detection/pipes/array_changes.js +++ b/modules/angular2/src/change_detection/pipes/array_changes.js @@ -16,6 +16,16 @@ import { 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 { _collection; _length:int; diff --git a/modules/angular2/src/change_detection/pipes/null_pipe.js b/modules/angular2/src/change_detection/pipes/null_pipe.js index 3e0766c630..1055619ff3 100644 --- a/modules/angular2/src/change_detection/pipes/null_pipe.js +++ b/modules/angular2/src/change_detection/pipes/null_pipe.js @@ -1,6 +1,16 @@ import {isBlank} from 'angular2/src/facade/lang'; 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 { called:boolean; constructor() { diff --git a/modules/angular2/src/change_detection/pipes/pipe_registry.js b/modules/angular2/src/change_detection/pipes/pipe_registry.js index 1a013bab20..f4d1a7b2c3 100644 --- a/modules/angular2/src/change_detection/pipes/pipe_registry.js +++ b/modules/angular2/src/change_detection/pipes/pipe_registry.js @@ -16,12 +16,12 @@ export class PipeRegistry { } var matchingConfig = ListWrapper.find(listOfConfigs, - (pipeConfig) => pipeConfig["supports"](obj)); + (pipeConfig) => pipeConfig.supports(obj)); if (isBlank(matchingConfig)) { throw new BaseException(`Cannot find a pipe for type '${type}' object '${obj}'`); } - return matchingConfig["pipe"](); + return matchingConfig.create(); } } \ No newline at end of file diff --git a/modules/angular2/src/change_detection/proto_change_detector.js b/modules/angular2/src/change_detection/proto_change_detector.js index c13b8103d2..30d2ade411 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.js +++ b/modules/angular2/src/change_detection/proto_change_detector.js @@ -9,9 +9,9 @@ import { AstVisitor, Binary, Chain, - Structural, Conditional, Formatter, + Pipe, FunctionCall, ImplicitReceiver, Interpolation, @@ -41,12 +41,12 @@ import { RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_INVOKE_FORMATTER, - RECORD_TYPE_STRUCTURAL_CHECK, + RECORD_TYPE_PIPE, RECORD_TYPE_INTERPOLATE } from './proto_record'; 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{ return null; } @@ -64,8 +64,8 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector { this._recordBuilder = new ProtoRecordBuilder(); } - addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) { - this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural); + addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) { + this._recordBuilder.addAst(ast, bindingMemento, directiveMemento); } instantiate(dispatcher:any, formatters:Map) { @@ -95,8 +95,8 @@ export class JitProtoChangeDetector extends ProtoChangeDetector { this._recordBuilder = new ProtoRecordBuilder(); } - addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) { - this._recordBuilder.addAst(ast, bindingMemento, directiveMemento, structural); + addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) { + this._recordBuilder.addAst(ast, bindingMemento, directiveMemento); } instantiate(dispatcher:any, formatters:Map) { @@ -121,9 +121,7 @@ class ProtoRecordBuilder { this.records = []; } - addAst(ast:AST, bindingMemento:any, directiveMemento:any = null, structural:boolean = false) { - if (structural) ast = new Structural(ast); - + addAst(ast:AST, bindingMemento:any, directiveMemento:any = null) { var last = ListWrapper.last(this.records); if (isPresent(last) && last.directiveMemento == directiveMemento) { last.lastInDirective = false; @@ -228,9 +226,9 @@ class _ConvertAstIntoProtoRecords { ChangeDetectionUtil.cond, [c,t,f], null, 0); } - visitStructural(ast:Structural) { - var value = ast.value.visit(this); - return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value); + visitPipe(ast:Pipe) { + var value = ast.exp.visit(this); + return this._addRecord(RECORD_TYPE_PIPE, ast.name, ast.name, [], null, value); } visitKeyedAccess(ast:KeyedAccess) { diff --git a/modules/angular2/src/change_detection/proto_record.js b/modules/angular2/src/change_detection/proto_record.js index 9aa77ba9ea..05d8edb7f4 100644 --- a/modules/angular2/src/change_detection/proto_record.js +++ b/modules/angular2/src/change_detection/proto_record.js @@ -8,7 +8,7 @@ export const RECORD_TYPE_INVOKE_METHOD = 4; export const RECORD_TYPE_INVOKE_CLOSURE = 5; export const RECORD_TYPE_KEYED_ACCESS = 6; export const RECORD_TYPE_INVOKE_FORMATTER = 7; -export const RECORD_TYPE_STRUCTURAL_CHECK = 8; +export const RECORD_TYPE_PIPE = 8; export const RECORD_TYPE_INTERPOLATE = 9; export class ProtoRecord { diff --git a/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js b/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js index d04c0b1060..49ed59dac0 100644 --- a/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js +++ b/modules/angular2/src/core/compiler/pipeline/element_binder_builder.js @@ -195,34 +195,38 @@ export class ElementBinderBuilder extends CompileStep { var directive = ListWrapper.get(directives, directiveIndex); var annotation = directive.annotation; if (isBlank(annotation.bind)) continue; - var _this = this; - StringMapWrapper.forEach(annotation.bind, function (elProp, dirProp) { - var expression = isPresent(compileElement.propertyBindings) ? + StringMapWrapper.forEach(annotation.bind, (bindConfig, dirProp) => { + var bindConfigParts = this._splitBindConfig(bindConfig); + var elProp = bindConfigParts[0]; + var pipes = ListWrapper.slice(bindConfigParts, 1, bindConfigParts.length); + + var bindingAst = isPresent(compileElement.propertyBindings) ? MapWrapper.get(compileElement.propertyBindings, elProp) : null; - if (isBlank(expression)) { + + if (isBlank(bindingAst)) { var attributeValue = MapWrapper.get(compileElement.attrs(), elProp); 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. - if (isPresent(expression)) { - var len = dirProp.length; - var dirBindingName = dirProp; - var isContentWatch = dirProp[len - 2] === '[' && dirProp[len - 1] === ']'; - if (isContentWatch) dirBindingName = dirProp.substring(0, len - 2); - + if (isPresent(bindingAst)) { + var fullExpAstWithBindPipes = this._parser.addPipes(bindingAst, pipes); protoView.bindDirectiveProperty( directiveIndex, - expression, - dirBindingName, - reflector.setter(dirBindingName), - isContentWatch + fullExpAstWithBindPipes, + dirProp, + reflector.setter(dirProp) ); } }); } } -} + + _splitBindConfig(bindConfig:string) { + var parts = StringWrapper.split(bindConfig, RegExpWrapper.create("\\|")); + return ListWrapper.map(parts, (s) => s.trim()); + } +} \ No newline at end of file diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index b190c36254..5f6330e699 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -511,8 +511,7 @@ export class ProtoView { directiveIndex:number, expression:AST, setterName:string, - setter:SetterFn, - isContentWatch: boolean) { + setter:SetterFn) { var bindingMemento = new DirectiveBindingMemento( this.elementBinders.length-1, @@ -521,7 +520,7 @@ export class ProtoView { setter ); 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 , diff --git a/modules/angular2/src/directives/foreach.js b/modules/angular2/src/directives/foreach.js index 09b1b770fd..44ca00b048 100644 --- a/modules/angular2/src/directives/foreach.js +++ b/modules/angular2/src/directives/foreach.js @@ -7,7 +7,7 @@ import {ListWrapper} from 'angular2/src/facade/collection'; @Viewport({ selector: '[foreach][in]', bind: { - 'iterableChanges[]': 'in' + 'iterableChanges': 'in | iterableDiff' } }) export class Foreach { diff --git a/modules/angular2/test/change_detection/change_detection_spec.js b/modules/angular2/test/change_detection/change_detection_spec.js index 06d8fbc29f..9c020c9d1d 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.js +++ b/modules/angular2/test/change_detection/change_detection_spec.js @@ -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 {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 {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, - registry = null, structural = false) { + registry = null, pipeType:string = null) { 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 cd = pcd.instantiate(dispatcher, formatters); @@ -40,9 +45,8 @@ export function main() { return {"changeDetector" : cd, "dispatcher" : dispatcher}; } - function executeWatch(memo:string, exp:string, context = null, formatters = null, - registry = null, content = false) { - var res = createChangeDetector(memo, exp, context, formatters, registry, content); + function executeWatch(memo:string, exp:string, context = null, formatters = null) { + var res = createChangeDetector(memo, exp, context, formatters); res["changeDetector"].detectChanges(); return res["dispatcher"].log; } @@ -190,7 +194,7 @@ export function main() { var parser = new Parser(new Lexer()); var pcd = createProtoChangeDetector(); var ast = parser.parseInterpolation("B{{a}}A", "location"); - pcd.addAst(ast, "memo", "memo", false); + pcd.addAst(ast, "memo", "memo"); var dispatcher = new TestDispatcher(); var cd = pcd.instantiate(dispatcher, MapWrapper.create()); @@ -428,10 +432,10 @@ export function main() { describe("pipes", () => { it("should support pipes", () => { - var registry = new FakePipeRegistry(() => new CountingPipe()); + var registry = new FakePipeRegistry('pipe', () => new CountingPipe()); 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 dispatcher = c["dispatcher"]; @@ -446,10 +450,10 @@ export function main() { }); 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 c = createChangeDetector("memo", "name", ctx, null, registry, true); + var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe'); var cd = c["changeDetector"]; cd.detectChanges(); @@ -464,10 +468,10 @@ export function main() { }); 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 c = createChangeDetector("memo", "name", ctx, null, registry, true); + var c = createChangeDetector("memo", "name", ctx, null, registry, 'pipe'); var cd = c["changeDetector"]; var dispatcher = c["dispatcher"]; @@ -537,15 +541,18 @@ class IdentityPipe { class FakePipeRegistry extends PipeRegistry { numberOfLookups:number; + pipeType:string; factory:Function; - constructor(factory) { + constructor(pipeType, factory) { super({}); + this.pipeType = pipeType; this.factory = factory; this.numberOfLookups = 0; } get(type:string, obj) { + if (type != this.pipeType) return null; this.numberOfLookups ++; return this.factory(); } diff --git a/modules/angular2/test/change_detection/parser/parser_spec.js b/modules/angular2/test/change_detection/parser/parser_spec.js index 3ddbe628cd..925a0981d6 100644 --- a/modules/angular2/test/change_detection/parser/parser_spec.js +++ b/modules/angular2/test/change_detection/parser/parser_spec.js @@ -51,6 +51,10 @@ export function main() { return createParser().parseInterpolation(text, location); } + function addPipes(ast, pipes) { + return createParser().addPipes(ast, pipes); + } + function expectEval(text, passedInContext = null) { var c = isBlank(passedInContext) ? td() : passedInContext; 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', () => { it('should wrap a literal primitive', () => { expect(createParser().wrapLiteralPrimitive("foo", null).eval(null)).toEqual("foo"); diff --git a/modules/angular2/test/change_detection/transformer_registry_spec.js b/modules/angular2/test/change_detection/pipe_registry_spec.js similarity index 74% rename from modules/angular2/test/change_detection/transformer_registry_spec.js rename to modules/angular2/test/change_detection/pipe_registry_spec.js index d62451ec7d..3c0d9d4cc4 100644 --- a/modules/angular2/test/change_detection/transformer_registry_spec.js +++ b/modules/angular2/test/change_detection/pipe_registry_spec.js @@ -11,8 +11,8 @@ export function main() { 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} + new PipeFactory(false, firstPipe), + 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; + } +} \ No newline at end of file diff --git a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js index 432a87f81f..b57d9540f9 100644 --- a/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/element_binder_builder_spec.js @@ -1,5 +1,5 @@ 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 {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 {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'; import {Injector} from 'angular2/di'; @@ -23,8 +23,7 @@ export function main() { describe('ElementBinderBuilder', () => { var evalContext, view, changeDetector; - function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector - }={}) { + function createPipeline({textNodeBindings, propertyBindings, eventBindings, directives, protoElementInjector, registry}={}) { var reflector = new DirectiveMetadataReader(); var parser = new Parser(new Lexer()); return new CompilePipeline([ @@ -70,8 +69,10 @@ export function main() { } if (isPresent(current.element.getAttribute('viewroot'))) { current.isViewRoot = true; - current.inheritedProtoView = new ProtoView(current.element, - new DynamicProtoChangeDetector(null), new NativeShadowDomStrategy()); + current.inheritedProtoView = new ProtoView( + current.element, + new DynamicProtoChangeDetector(normalizeBlank(registry)), + new NativeShadowDomStrategy()); } else if (isPresent(parent)) { current.inheritedProtoView = parent.inheritedProtoView; } @@ -393,6 +394,37 @@ export function main() { 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('
')); + var pv = results[0].inheritedProtoView; + results[0].inheritedElementBinder.nestedProtoView = new ProtoView( + el('
'), 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', () => { var propertyBindings = MapWrapper.createFromStringMap({ '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 { prop1; prop2; diff --git a/modules/angular2/test/core/compiler/view_spec.js b/modules/angular2/test/core/compiler/view_spec.js index af25e86740..dcbbc37dc1 100644 --- a/modules/angular2/test/core/compiler/view_spec.js +++ b/modules/angular2/test/core/compiler/view_spec.js @@ -548,7 +548,7 @@ export function main() { var pv = new ProtoView(el('
'), new DynamicProtoChangeDetector(null), null); 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); ctx.foo = 'buz'; @@ -563,8 +563,8 @@ export function main() { pv.bindElement(new ProtoElementInjector(null, 0, [ DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]})) ])); - pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false); - pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false); + pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a')); + pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b')); createViewAndChangeDetector(pv); ctx.a = 100; @@ -582,8 +582,8 @@ export function main() { pv.bindElement(new ProtoElementInjector(null, 0, [ DirectiveBinding.createFromType(DirectiveImplementingOnChange, new Directive({lifecycle: [onChange]})) ])); - pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a'), false); - pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b'), false); + pv.bindDirectiveProperty( 0, parser.parseBinding('a', null), 'a', reflector.setter('a')); + pv.bindDirectiveProperty( 0, parser.parseBinding('b', null), 'b', reflector.setter('b')); createViewAndChangeDetector(pv); ctx.a = 0; diff --git a/modules/benchmarks/src/change_detection/change_detection_benchmark.js b/modules/benchmarks/src/change_detection/change_detection_benchmark.js index 3556199596..03abf9d80e 100644 --- a/modules/benchmarks/src/change_detection/change_detection_benchmark.js +++ b/modules/benchmarks/src/change_detection/change_detection_benchmark.js @@ -119,7 +119,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) { parser.parseBinding('field9', null) ]; 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) { diff --git a/modules/benchmarks/src/naive_infinite_scroll/index.js b/modules/benchmarks/src/naive_infinite_scroll/index.js index bb827d7cef..7c140ad349 100644 --- a/modules/benchmarks/src/naive_infinite_scroll/index.js +++ b/modules/benchmarks/src/naive_infinite_scroll/index.js @@ -169,7 +169,7 @@ export function setupReflectorForAngular() { 'annotations' : [new Viewport({ selector: '[foreach]', bind: { - 'iterableChanges[]': 'in' + 'iterableChanges': 'in | iterableDiff' } })] });