feat(change_detection): add support for pipes in the template

This commit is contained in:
vsavkin 2015-02-20 10:59:14 -08:00
parent 29f5ee0c29
commit 987a5fdf56
12 changed files with 138 additions and 134 deletions

View File

@ -35,19 +35,33 @@ export var defaultPipes = {
] ]
}; };
var _registry = new PipeRegistry(defaultPipes);
export class DynamicChangeDetection extends ChangeDetection { export class DynamicChangeDetection extends ChangeDetection {
registry:PipeRegistry;
constructor(registry:PipeRegistry) {
super();
this.registry = registry;
}
createProtoChangeDetector(name:string):ProtoChangeDetector{ createProtoChangeDetector(name:string):ProtoChangeDetector{
return new DynamicProtoChangeDetector(_registry); return new DynamicProtoChangeDetector(this.registry);
} }
} }
export class JitChangeDetection extends ChangeDetection { export class JitChangeDetection extends ChangeDetection {
registry:PipeRegistry;
constructor(registry:PipeRegistry) {
super();
this.registry = registry;
}
createProtoChangeDetector(name:string):ProtoChangeDetector{ createProtoChangeDetector(name:string):ProtoChangeDetector{
return new JitProtoChangeDetector(_registry); return new JitProtoChangeDetector(this.registry);
} }
} }
export var dynamicChangeDetection = new DynamicChangeDetection(); var _registry = new PipeRegistry(defaultPipes);
export var jitChangeDetection = new JitChangeDetection();
export var dynamicChangeDetection = new DynamicChangeDetection(_registry);
export var jitChangeDetection = new JitChangeDetection(_registry);

View File

@ -14,7 +14,6 @@ import {
RECORD_TYPE_INVOKE_CLOSURE, RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_PIPE, RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
@ -26,10 +25,9 @@ import {
* *
* For example: An expression `address.city` will result in the following class: * For example: An expression `address.city` will result in the following class:
* *
* var ChangeDetector0 = function ChangeDetector0(dispatcher, formatters, protos) { * var ChangeDetector0 = function ChangeDetector0(dispatcher, protos) {
* AbstractChangeDetector.call(this); * AbstractChangeDetector.call(this);
* this.dispatcher = dispatcher; * this.dispatcher = dispatcher;
* this.formatters = formatters;
* this.protos = protos; * this.protos = protos;
* *
* this.context = null; * this.context = null;
@ -89,12 +87,11 @@ import {
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector"; var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil"; var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher"; var DISPATCHER_ACCESSOR = "this.dispatcher";
var FORMATTERS_ACCESSOR = "this.formatters"; var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry";
var PROTOS_ACCESSOR = "this.protos"; 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 +99,17 @@ ${cons}
${detectChanges} ${detectChanges}
${setContext}; ${setContext};
return function(dispatcher, formatters, pipeRegistry) { return function(dispatcher, pipeRegistry) {
return new ${type}(dispatcher, formatters, pipeRegistry, protos); return new ${type}(dispatcher, pipeRegistry, protos);
} }
`; `;
} }
function constructorTemplate(type:string, fieldsDefinitions:string):string { function constructorTemplate(type:string, fieldsDefinitions:string):string {
return ` return `
var ${type} = function ${type}(dispatcher, formatters, pipeRegistry, protos) { var ${type} = function ${type}(dispatcher, pipeRegistry, protos) {
${ABSTRACT_CHANGE_DETECTOR}.call(this); ${ABSTRACT_CHANGE_DETECTOR}.call(this);
${DISPATCHER_ACCESSOR} = dispatcher; ${DISPATCHER_ACCESSOR} = dispatcher;
${FORMATTERS_ACCESSOR} = formatters;
${PIPE_REGISTRY_ACCESSOR} = pipeRegistry; ${PIPE_REGISTRY_ACCESSOR} = pipeRegistry;
${PROTOS_ACCESSOR} = protos; ${PROTOS_ACCESSOR} = protos;
${fieldsDefinitions} ${fieldsDefinitions}
@ -381,9 +377,6 @@ export class ChangeDetectorJITGenerator {
case RECORD_TYPE_INTERPOLATE: case RECORD_TYPE_INTERPOLATE:
return assignmentTemplate(newValue, this.genInterpolation(r)); return assignmentTemplate(newValue, this.genInterpolation(r));
case RECORD_TYPE_INVOKE_FORMATTER:
return assignmentTemplate(newValue, `${FORMATTERS_ACCESSOR}.get("${r.name}")(${args})`);
case RECORD_TYPE_KEYED_ACCESS: case RECORD_TYPE_KEYED_ACCESS:
var key = this.localNames[r.args[0]]; var key = this.localNames[r.args[0]];
return assignmentTemplate(newValue, `${context}[${key}]`); return assignmentTemplate(newValue, `${context}[${key}]`);

View File

@ -16,7 +16,6 @@ import {
RECORD_TYPE_INVOKE_CLOSURE, RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_PIPE, RECORD_TYPE_PIPE,
RECORD_TYPE_INTERPOLATE RECORD_TYPE_INTERPOLATE
} from './proto_record'; } from './proto_record';
@ -25,7 +24,6 @@ import {ExpressionChangedAfterItHasBeenChecked, ChangeDetectionError} from './ex
export class DynamicChangeDetector extends AbstractChangeDetector { export class DynamicChangeDetector extends AbstractChangeDetector {
dispatcher:any; dispatcher:any;
formatters:Map;
pipeRegistry; pipeRegistry;
values:List; values:List;
@ -35,10 +33,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
protos:List<ProtoRecord>; protos:List<ProtoRecord>;
constructor(dispatcher:any, formatters:Map, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>) { constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>) {
super(); super();
this.dispatcher = dispatcher; this.dispatcher = dispatcher;
this.formatters = formatters;
this.pipeRegistry = pipeRegistry; this.pipeRegistry = pipeRegistry;
this.values = ListWrapper.createFixedSize(protoRecords.length + 1); this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
@ -149,10 +146,6 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
case RECORD_TYPE_PRIMITIVE_OP: case RECORD_TYPE_PRIMITIVE_OP:
return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto)); return FunctionWrapper.apply(proto.funcOrValue, this._readArgs(proto));
case RECORD_TYPE_INVOKE_FORMATTER:
var formatter = MapWrapper.get(this.formatters, proto.funcOrValue);
return FunctionWrapper.apply(formatter, this._readArgs(proto));
default: default:
throw new BaseException(`Unknown operation ${proto.mode}`); throw new BaseException(`Unknown operation ${proto.mode}`);
} }

View File

@ -170,31 +170,15 @@ export class KeyedAccess extends AST {
} }
} }
export class Formatter extends AST { export class Pipe extends AST {
exp:AST; exp:AST;
name:string; name:string;
args:List<AST>; args:List<AST>;
allArgs:List<AST>;
constructor(exp:AST, name:string, args:List) { constructor(exp:AST, name:string, args:List) {
super(); super();
this.exp = exp; this.exp = exp;
this.name = name; this.name = name;
this.args = args; this.args = args;
this.allArgs = ListWrapper.concat([exp], args);
}
visit(visitor) {
return visitor.visitFormatter(this);
}
}
export class Pipe extends AST {
exp:AST;
name:string;
constructor(exp:AST, name:string) {
super();
this.exp = exp;
this.name = name;
} }
visit(visitor) { visit(visitor) {
@ -459,7 +443,6 @@ export class AstVisitor {
visitBinary(ast:Binary) {} visitBinary(ast:Binary) {}
visitChain(ast:Chain){} visitChain(ast:Chain){}
visitConditional(ast:Conditional) {} visitConditional(ast:Conditional) {}
visitFormatter(ast:Formatter) {}
visitPipe(ast:Pipe) {} visitPipe(ast:Pipe) {}
visitFunctionCall(ast:FunctionCall) {} visitFunctionCall(ast:FunctionCall) {}
visitImplicitReceiver(ast:ImplicitReceiver) {} visitImplicitReceiver(ast:ImplicitReceiver) {}

View File

@ -13,7 +13,6 @@ import {
Binary, Binary,
PrefixNot, PrefixNot,
Conditional, Conditional,
Formatter,
Pipe, Pipe,
Assignment, Assignment,
Chain, Chain,
@ -57,7 +56,7 @@ export class Parser {
if (ListWrapper.isEmpty(pipes)) return bindingAst; if (ListWrapper.isEmpty(pipes)) return bindingAst;
var res = ListWrapper.reduce(pipes, var res = ListWrapper.reduce(pipes,
(result, currentPipeName) => new Pipe(result, currentPipeName), (result, currentPipeName) => new Pipe(result, currentPipeName, []),
bindingAst.ast); bindingAst.ast);
return new ASTWithSource(res, bindingAst.source, bindingAst.location); return new ASTWithSource(res, bindingAst.source, bindingAst.location);
} }
@ -191,7 +190,7 @@ class _ParseAST {
parseChain():AST { parseChain():AST {
var exprs = []; var exprs = [];
while (this.index < this.tokens.length) { while (this.index < this.tokens.length) {
var expr = this.parseFormatter(); var expr = this.parsePipe();
ListWrapper.push(exprs, expr); ListWrapper.push(exprs, expr);
if (this.optionalCharacter($SEMICOLON)) { if (this.optionalCharacter($SEMICOLON)) {
@ -208,18 +207,18 @@ class _ParseAST {
return new Chain(exprs); return new Chain(exprs);
} }
parseFormatter() { parsePipe() {
var result = this.parseExpression(); var result = this.parseExpression();
while (this.optionalOperator("|")) { while (this.optionalOperator("|")) {
if (this.parseAction) { if (this.parseAction) {
this.error("Cannot have a formatter in an action expression"); this.error("Cannot have a pipe in an action expression");
} }
var name = this.expectIdentifierOrKeyword(); var name = this.expectIdentifierOrKeyword();
var args = ListWrapper.create(); var args = ListWrapper.create();
while (this.optionalCharacter($COLON)) { while (this.optionalCharacter($COLON)) {
ListWrapper.push(args, this.parseExpression()); ListWrapper.push(args, this.parseExpression());
} }
result = new Formatter(result, name, args); result = new Pipe(result, name, args);
} }
return result; return result;
} }
@ -380,7 +379,7 @@ class _ParseAST {
parsePrimary() { parsePrimary() {
if (this.optionalCharacter($LPAREN)) { if (this.optionalCharacter($LPAREN)) {
var result = this.parseFormatter(); var result = this.parsePipe();
this.expectCharacter($RPAREN); this.expectCharacter($RPAREN);
return result; return result;

View File

@ -10,7 +10,6 @@ import {
Binary, Binary,
Chain, Chain,
Conditional, Conditional,
Formatter,
Pipe, Pipe,
FunctionCall, FunctionCall,
ImplicitReceiver, ImplicitReceiver,
@ -40,14 +39,13 @@ import {
RECORD_TYPE_INVOKE_CLOSURE, RECORD_TYPE_INVOKE_CLOSURE,
RECORD_TYPE_PRIMITIVE_OP, RECORD_TYPE_PRIMITIVE_OP,
RECORD_TYPE_KEYED_ACCESS, RECORD_TYPE_KEYED_ACCESS,
RECORD_TYPE_INVOKE_FORMATTER,
RECORD_TYPE_PIPE, 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){} addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
instantiate(dispatcher:any, formatters:Map):ChangeDetector{ instantiate(dispatcher:any):ChangeDetector{
return null; return null;
} }
} }
@ -68,10 +66,9 @@ export class DynamicProtoChangeDetector extends ProtoChangeDetector {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento); this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
} }
instantiate(dispatcher:any, formatters:Map) { instantiate(dispatcher:any) {
this._createRecordsIfNecessary(); this._createRecordsIfNecessary();
return new DynamicChangeDetector(dispatcher, formatters, return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records);
this._pipeRegistry, this._records);
} }
_createRecordsIfNecessary() { _createRecordsIfNecessary() {
@ -99,9 +96,9 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
this._recordBuilder.addAst(ast, bindingMemento, directiveMemento); this._recordBuilder.addAst(ast, bindingMemento, directiveMemento);
} }
instantiate(dispatcher:any, formatters:Map) { instantiate(dispatcher:any) {
this._createFactoryIfNecessary(); this._createFactoryIfNecessary();
return this._factory(dispatcher, formatters, this._pipeRegistry); return this._factory(dispatcher, this._pipeRegistry);
} }
_createFactoryIfNecessary() { _createFactoryIfNecessary() {
@ -178,10 +175,6 @@ class _ConvertAstIntoProtoRecords {
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver); return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
} }
visitFormatter(ast:Formatter) {
return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), null, 0);
}
visitMethodCall(ast:MethodCall) { visitMethodCall(ast:MethodCall) {
var receiver = ast.receiver.visit(this); var receiver = ast.receiver.visit(this);
var args = this._visitAll(ast.args); var args = this._visitAll(ast.args);

View File

@ -7,7 +7,6 @@ export const RECORD_TYPE_PROPERTY = 3;
export const RECORD_TYPE_INVOKE_METHOD = 4; 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_PIPE = 8; export const RECORD_TYPE_PIPE = 8;
export const RECORD_TYPE_INTERPOLATE = 9; export const RECORD_TYPE_INTERPOLATE = 9;
@ -54,7 +53,6 @@ export class ProtoRecord {
isPureFunction():boolean { isPureFunction():boolean {
return this.mode === RECORD_TYPE_INTERPOLATE || return this.mode === RECORD_TYPE_INTERPOLATE ||
this.mode === RECORD_TYPE_INVOKE_FORMATTER ||
this.mode === RECORD_TYPE_PRIMITIVE_OP; this.mode === RECORD_TYPE_PRIMITIVE_OP;
} }
} }

View File

@ -19,8 +19,6 @@ import {EventManager} from 'angular2/src/core/events/event_manager';
const NG_BINDING_CLASS = 'ng-binding'; const NG_BINDING_CLASS = 'ng-binding';
const NG_BINDING_CLASS_SELECTOR = '.ng-binding'; const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
// TODO(tbosch): Cannot use `const` because of Dart.
var NO_FORMATTERS = MapWrapper.create();
// TODO(rado): make this configurable/smarter. // TODO(rado): make this configurable/smarter.
var VIEW_POOL_CAPACITY = 10000; var VIEW_POOL_CAPACITY = 10000;
@ -50,7 +48,7 @@ export class View {
constructor(proto:ProtoView, nodes:List<Node>, protoChangeDetector:ProtoChangeDetector, protoContextLocals:Map) { constructor(proto:ProtoView, nodes:List<Node>, protoChangeDetector:ProtoChangeDetector, protoContextLocals:Map) {
this.proto = proto; this.proto = proto;
this.nodes = nodes; this.nodes = nodes;
this.changeDetector = protoChangeDetector.instantiate(this, NO_FORMATTERS); this.changeDetector = protoChangeDetector.instantiate(this);
this.elementInjectors = null; this.elementInjectors = null;
this.rootElementInjectors = null; this.rootElementInjectors = null;
this.textNodes = null; this.textNodes = null;

View File

@ -3,7 +3,6 @@ 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,24 +28,18 @@ export function main() {
return parser.parseBinding(exp, location); return parser.parseBinding(exp, location);
} }
function createChangeDetector(memo:string, exp:string, context = null, formatters = null, function createChangeDetector(memo:string, exp:string, context = null, registry = null) {
registry = null, pipeType:string = null) {
var pcd = createProtoChangeDetector(registry); var pcd = createProtoChangeDetector(registry);
var parsedAst = ast(exp); pcd.addAst(ast(exp), memo, memo);
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);
cd.setContext(context); cd.setContext(context);
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) {
var res = createChangeDetector(memo, exp, context, formatters); var res = createChangeDetector(memo, exp, context);
res["changeDetector"].detectChanges(); res["changeDetector"].detectChanges();
return res["dispatcher"].log; return res["dispatcher"].log;
} }
@ -182,14 +175,6 @@ export function main() {
}); });
}); });
it("should support formatters", () => {
var formatters = MapWrapper.createFromPairs([
['uppercase', (v) => v.toUpperCase()],
['wrap', (v, before, after) => `${before}${v}${after}`]]);
expect(executeWatch('str', '"aBc" | uppercase', null, formatters)).toEqual(['str=ABC']);
expect(executeWatch('str', '"b" | wrap:"a":"c"', null, formatters)).toEqual(['str=abc']);
});
it("should support interpolation", () => { it("should support interpolation", () => {
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
@ -197,7 +182,7 @@ export function main() {
pcd.addAst(ast, "memo", "memo"); 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);
cd.setContext(new TestData("value")); cd.setContext(new TestData("value"));
cd.detectChanges(); cd.detectChanges();
@ -213,7 +198,7 @@ export function main() {
pcd.addAst(ast("100 + 200"), "memo2", "2"); pcd.addAst(ast("100 + 200"), "memo2", "2");
var dispatcher = new TestDispatcher(); var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null); var cd = pcd.instantiate(dispatcher);
cd.detectChanges(); cd.detectChanges();
@ -227,7 +212,7 @@ export function main() {
pcd.addAst(ast("c()"), "c", "2"); pcd.addAst(ast("c()"), "c", "2");
var dispatcher = new TestDispatcher(); var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null); var cd = pcd.instantiate(dispatcher);
var tr = new TestRecord(); var tr = new TestRecord();
tr.a = () => { tr.a = () => {
@ -256,7 +241,7 @@ export function main() {
pcd.addAst(ast("a"), "a", 1); pcd.addAst(ast("a"), "a", 1);
var dispatcher = new TestDispatcher(); var dispatcher = new TestDispatcher();
var cd = pcd.instantiate(dispatcher, null); var cd = pcd.instantiate(dispatcher);
cd.setContext(new TestData('value')); cd.setContext(new TestData('value'));
expect(() => { expect(() => {
@ -271,7 +256,7 @@ export function main() {
var pcd = createProtoChangeDetector(); var pcd = createProtoChangeDetector();
pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1); pcd.addAst(ast('invalidProp', 'someComponent'), "a", 1);
var cd = pcd.instantiate(new TestDispatcher(), null); var cd = pcd.instantiate(new TestDispatcher());
cd.setContext(null); cd.setContext(null);
try { try {
@ -318,10 +303,10 @@ export function main() {
beforeEach(() => { beforeEach(() => {
var protoParent = createProtoChangeDetector(); var protoParent = createProtoChangeDetector();
parent = protoParent.instantiate(null, null); parent = protoParent.instantiate(null);
var protoChild = createProtoChangeDetector(); var protoChild = createProtoChangeDetector();
child = protoChild.instantiate(null, null); child = protoChild.instantiate(null);
}); });
it("should add children", () => { it("should add children", () => {
@ -340,25 +325,6 @@ export function main() {
}); });
}); });
describe("optimizations", () => {
it("should not rerun formatters when args did not change", () => {
var count = 0;
var formatters = MapWrapper.createFromPairs([
['count', (v) => {count ++; "value"}]]);
var c = createChangeDetector('a', 'a | count', new TestData(null), formatters);
var cd = c["changeDetector"];
cd.detectChanges();
expect(count).toEqual(1);
cd.detectChanges();
expect(count).toEqual(1);
});
});
describe("mode", () => { describe("mode", () => {
it("should not check a detached change detector", () => { it("should not check a detached change detector", () => {
var c = createChangeDetector('name', 'a', new TestData("value")); var c = createChangeDetector('name', 'a', new TestData("value"));
@ -383,7 +349,7 @@ export function main() {
}); });
it("should change CHECK_ONCE to CHECKED", () => { it("should change CHECK_ONCE to CHECKED", () => {
var cd = createProtoChangeDetector().instantiate(null, null); var cd = createProtoChangeDetector().instantiate(null);
cd.mode = CHECK_ONCE; cd.mode = CHECK_ONCE;
cd.detectChanges(); cd.detectChanges();
@ -392,7 +358,7 @@ export function main() {
}); });
it("should not change the CHECK_ALWAYS", () => { it("should not change the CHECK_ALWAYS", () => {
var cd = createProtoChangeDetector().instantiate(null, null); var cd = createProtoChangeDetector().instantiate(null);
cd.mode = CHECK_ALWAYS; cd.mode = CHECK_ALWAYS;
cd.detectChanges(); cd.detectChanges();
@ -403,7 +369,7 @@ export function main() {
describe("markPathToRootAsCheckOnce", () => { describe("markPathToRootAsCheckOnce", () => {
function changeDetector(mode, parent) { function changeDetector(mode, parent) {
var cd = createProtoChangeDetector().instantiate(null, null); var cd = createProtoChangeDetector().instantiate(null);
cd.mode = mode; cd.mode = mode;
if (isPresent(parent)) parent.addChild(cd); if (isPresent(parent)) parent.addChild(cd);
return cd; return cd;
@ -435,7 +401,7 @@ export function main() {
var registry = new FakePipeRegistry('pipe', () => 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, 'pipe'); var c = createChangeDetector("memo", "name | pipe", ctx, registry);
var cd = c["changeDetector"]; var cd = c["changeDetector"];
var dispatcher = c["dispatcher"]; var dispatcher = c["dispatcher"];
@ -453,7 +419,7 @@ export function main() {
var registry = new FakePipeRegistry('pipe', () => 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, 'pipe'); var c = createChangeDetector("memo", "name | pipe", ctx, registry);
var cd = c["changeDetector"]; var cd = c["changeDetector"];
cd.detectChanges(); cd.detectChanges();
@ -471,7 +437,7 @@ export function main() {
var registry = new FakePipeRegistry('pipe', () => 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, 'pipe'); var c = createChangeDetector("memo", "name | pipe", ctx, registry);
var cd = c["changeDetector"]; var cd = c["changeDetector"];
var dispatcher = c["dispatcher"]; var dispatcher = c["dispatcher"];

View File

@ -5,7 +5,7 @@ import {MapWrapper, ListWrapper} from 'angular2/src/facade/collection';
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';
import {ContextWithVariableBindings} from 'angular2/src/change_detection/parser/context_with_variable_bindings'; import {ContextWithVariableBindings} from 'angular2/src/change_detection/parser/context_with_variable_bindings';
import {Formatter, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast'; import {Pipe, LiteralPrimitive} from 'angular2/src/change_detection/parser/ast';
class TestData { class TestData {
a; a;
@ -340,8 +340,8 @@ export function main() {
}); });
}); });
it("should error when using formatters", () => { it("should error when using pipes", () => {
expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a formatter')); expectEvalError('x|blah').toThrowError(new RegExp('Cannot have a pipe'));
}); });
it('should pass exceptions', () => { it('should pass exceptions', () => {
@ -367,16 +367,16 @@ export function main() {
}); });
describe("parseBinding", () => { describe("parseBinding", () => {
describe("formatters", () => { describe("pipes", () => {
it("should parse formatters", () => { it("should parse pipes", () => {
var exp = parseBinding("'Foo'|uppercase").ast; var exp = parseBinding("'Foo'|uppercase").ast;
expect(exp).toBeAnInstanceOf(Formatter); expect(exp).toBeAnInstanceOf(Pipe);
expect(exp.name).toEqual("uppercase"); expect(exp.name).toEqual("uppercase");
}); });
it("should parse formatters with args", () => { it("should parse pipes with args", () => {
var exp = parseBinding("1|increment:2").ast; var exp = parseBinding("1|increment:2").ast;
expect(exp).toBeAnInstanceOf(Formatter); expect(exp).toBeAnInstanceOf(Pipe);
expect(exp.name).toEqual("increment"); expect(exp.name).toEqual("increment");
expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive); expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive);
}); });

View File

@ -5,7 +5,8 @@ import {Map, MapWrapper} from 'angular2/src/facade/collection';
import {Type, isPresent} from 'angular2/src/facade/lang'; import {Type, isPresent} from 'angular2/src/facade/lang';
import {Injector} from 'angular2/di'; import {Injector} from 'angular2/di';
import {Lexer, Parser, ChangeDetector, dynamicChangeDetection} from 'angular2/change_detection'; import {Lexer, Parser, ChangeDetector, dynamicChangeDetection,
DynamicChangeDetection, Pipe, PipeRegistry} from 'angular2/change_detection';
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler'; import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader'; import {DirectiveMetadataReader} from 'angular2/src/core/compiler/directive_metadata_reader';
@ -24,9 +25,8 @@ export function main() {
describe('integration tests', function() { describe('integration tests', function() {
var compiler, tplResolver; var compiler, tplResolver;
beforeEach( () => { function createCompiler(tplResolver, changedDetection) {
tplResolver = new FakeTemplateResolver(); return new Compiler(changedDetection,
compiler = new Compiler(dynamicChangeDetection,
new TemplateLoader(null), new TemplateLoader(null),
new DirectiveMetadataReader(), new DirectiveMetadataReader(),
new Parser(new Lexer()), new Parser(new Lexer()),
@ -34,6 +34,11 @@ export function main() {
new NativeShadowDomStrategy(), new NativeShadowDomStrategy(),
tplResolver tplResolver
); );
}
beforeEach( () => {
tplResolver = new FakeTemplateResolver();
compiler = createCompiler(tplResolver, dynamicChangeDetection);
}); });
describe('react to record changes', function() { describe('react to record changes', function() {
@ -114,6 +119,33 @@ export function main() {
}); });
}); });
it("should support pipes in bindings and bind config", (done) => {
tplResolver.setTemplate(MyComp,
new Template({
inline: '<component-with-pipes #comp [prop]="ctxProp | double"></component-with-pipes>',
directives: [ComponentWithPipes]
}));
var registry = new PipeRegistry({
"double" : [new DoublePipeFactory()]
});
var changeDetection = new DynamicChangeDetection(registry);
var compiler = createCompiler(tplResolver, changeDetection);
compiler.compile(MyComp).then((pv) => {
createView(pv);
ctx.ctxProp = 'a';
cd.detectChanges();
var comp = view.contextWithLocals.get("comp");
// it is doubled twice: once in the binding, second time in the bind config
expect(comp.prop).toEqual('aaaa');
done();
});
});
it('should support nested components.', (done) => { it('should support nested components.', (done) => {
tplResolver.setTemplate(MyComp, new Template({ tplResolver.setTemplate(MyComp, new Template({
inline: '<child-cmp></child-cmp>', inline: '<child-cmp></child-cmp>',
@ -379,6 +411,20 @@ class MyComp {
} }
} }
@Component({
selector: 'component-with-pipes',
bind: {
"prop": "prop | double"
}
})
@Template({
inline: ''
})
class ComponentWithPipes {
prop:string;
}
@Component({ @Component({
selector: 'child-cmp', selector: 'child-cmp',
componentServices: [MyService] componentServices: [MyService]
@ -468,3 +514,24 @@ class FakeTemplateResolver extends TemplateResolver {
return super.resolve(component); return super.resolve(component);
} }
} }
class DoublePipe extends Pipe {
supports(obj) {
return true;
}
transform(value) {
return `${value}${value}`;
}
}
class DoublePipeFactory {
supports(obj) {
return true;
}
create() {
return new DoublePipe();
}
}

View File

@ -103,7 +103,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) {
var parser = new Parser(new Lexer()); var parser = new Parser(new Lexer());
var parentProto = changeDetection.createProtoChangeDetector('parent'); var parentProto = changeDetection.createProtoChangeDetector('parent');
var parentCd = parentProto.instantiate(dispatcher, MapWrapper.create()); var parentCd = parentProto.instantiate(dispatcher);
var proto = changeDetection.createProtoChangeDetector("proto"); var proto = changeDetection.createProtoChangeDetector("proto");
var astWithSource = [ var astWithSource = [
@ -127,7 +127,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations) {
for (var j = 0; j < 10; ++j) { for (var j = 0; j < 10; ++j) {
obj.setField(j, i); obj.setField(j, i);
} }
var cd = proto.instantiate(dispatcher, null); var cd = proto.instantiate(dispatcher);
cd.setContext(obj); cd.setContext(obj);
parentCd.addChild(cd); parentCd.addChild(cd);
} }