fix(compiler): properly implement pure pipes and change pipe syntax

Pure pipes as well as arrays and maps are
implemented via proxy functions. This is
faster than the previous implementation
and also generates less code.

BREAKING CHANGE:
- pipes now take a variable number of arguments, and not an array that contains all arguments.
This commit is contained in:
Tobias Bosch 2016-04-22 15:33:32 -07:00
parent d6626309fd
commit 152a117d5c
48 changed files with 698 additions and 283 deletions

View File

@ -1,13 +1,6 @@
import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang'; import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang';
import {ObservableWrapper, Observable, EventEmitter} from 'angular2/src/facade/async'; import {ObservableWrapper, Observable, EventEmitter} from 'angular2/src/facade/async';
import { import {Pipe, Injectable, ChangeDetectorRef, OnDestroy, WrappedValue} from 'angular2/core';
Pipe,
Injectable,
ChangeDetectorRef,
OnDestroy,
PipeTransform,
WrappedValue
} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@ -55,7 +48,7 @@ var __unused: Promise<any>; // avoid unused import when Promise union types are
*/ */
@Pipe({name: 'async', pure: false}) @Pipe({name: 'async', pure: false})
@Injectable() @Injectable()
export class AsyncPipe implements PipeTransform, OnDestroy { export class AsyncPipe implements OnDestroy {
/** @internal */ /** @internal */
_latestValue: Object = null; _latestValue: Object = null;
/** @internal */ /** @internal */
@ -76,7 +69,7 @@ export class AsyncPipe implements PipeTransform, OnDestroy {
} }
} }
transform(obj: Observable<any>| Promise<any>| EventEmitter<any>, args?: any[]): any { transform(obj: Observable<any>| Promise<any>| EventEmitter<any>): any {
if (isBlank(this._obj)) { if (isBlank(this._obj)) {
if (isPresent(obj)) { if (isPresent(obj)) {
this._subscribe(obj); this._subscribe(obj);

View File

@ -101,14 +101,13 @@ export class DatePipe implements PipeTransform {
}; };
transform(value: any, args: any[]): string { transform(value: any, pattern: string = 'mediumDate'): string {
if (isBlank(value)) return null; if (isBlank(value)) return null;
if (!this.supports(value)) { if (!this.supports(value)) {
throw new InvalidPipeArgumentException(DatePipe, value); throw new InvalidPipeArgumentException(DatePipe, value);
} }
var pattern: string = isPresent(args) && args.length > 0 ? args[0] : 'mediumDate';
if (isNumber(value)) { if (isNumber(value)) {
value = DateWrapper.fromMillis(value); value = DateWrapper.fromMillis(value);
} }

View File

@ -45,10 +45,9 @@ var interpolationExp: RegExp = RegExpWrapper.create('#');
@Pipe({name: 'i18nPlural', pure: true}) @Pipe({name: 'i18nPlural', pure: true})
@Injectable() @Injectable()
export class I18nPluralPipe implements PipeTransform { export class I18nPluralPipe implements PipeTransform {
transform(value: number, args: any[] = null): string { transform(value: number, pluralMap: {[count: string]: string}): string {
var key: string; var key: string;
var valueStr: string; var valueStr: string;
var pluralMap: {[count: string]: string} = <{[count: string]: string}>(args[0]);
if (!isStringMap(pluralMap)) { if (!isStringMap(pluralMap)) {
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap); throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);

View File

@ -36,8 +36,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'i18nSelect', pure: true}) @Pipe({name: 'i18nSelect', pure: true})
@Injectable() @Injectable()
export class I18nSelectPipe implements PipeTransform { export class I18nSelectPipe implements PipeTransform {
transform(value: string, args: any[] = null): string { transform(value: string, mapping: {[key: string]: string}): string {
var mapping: {[key: string]: string} = <{[count: string]: string}>(args[0]);
if (!isStringMap(mapping)) { if (!isStringMap(mapping)) {
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping); throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
} }

View File

@ -11,5 +11,5 @@ import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core';
@Pipe({name: 'json', pure: false}) @Pipe({name: 'json', pure: false})
@Injectable() @Injectable()
export class JsonPipe implements PipeTransform { export class JsonPipe implements PipeTransform {
transform(value: any, args: any[] = null): string { return Json.stringify(value); } transform(value: any): string { return Json.stringify(value); }
} }

View File

@ -13,7 +13,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'lowercase'}) @Pipe({name: 'lowercase'})
@Injectable() @Injectable()
export class LowerCasePipe implements PipeTransform { export class LowerCasePipe implements PipeTransform {
transform(value: string, args: any[] = null): string { transform(value: string): string {
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!isString(value)) { if (!isString(value)) {
throw new InvalidPipeArgumentException(LowerCasePipe, value); throw new InvalidPipeArgumentException(LowerCasePipe, value);

View File

@ -11,7 +11,6 @@ import {
import {BaseException, WrappedException} from 'angular2/src/facade/exceptions'; import {BaseException, WrappedException} from 'angular2/src/facade/exceptions';
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl'; import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core'; import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core';
import {ListWrapper} from 'angular2/src/facade/collection';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@ -87,8 +86,7 @@ export class NumberPipe {
@Pipe({name: 'number'}) @Pipe({name: 'number'})
@Injectable() @Injectable()
export class DecimalPipe extends NumberPipe implements PipeTransform { export class DecimalPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string { transform(value: any, digits: string = null): string {
var digits: string = ListWrapper.first(args);
return NumberPipe._format(value, NumberFormatStyle.Decimal, digits); return NumberPipe._format(value, NumberFormatStyle.Decimal, digits);
} }
} }
@ -113,8 +111,7 @@ export class DecimalPipe extends NumberPipe implements PipeTransform {
@Pipe({name: 'percent'}) @Pipe({name: 'percent'})
@Injectable() @Injectable()
export class PercentPipe extends NumberPipe implements PipeTransform { export class PercentPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string { transform(value: any, digits: string = null): string {
var digits: string = ListWrapper.first(args);
return NumberPipe._format(value, NumberFormatStyle.Percent, digits); return NumberPipe._format(value, NumberFormatStyle.Percent, digits);
} }
} }
@ -143,10 +140,8 @@ export class PercentPipe extends NumberPipe implements PipeTransform {
@Pipe({name: 'currency'}) @Pipe({name: 'currency'})
@Injectable() @Injectable()
export class CurrencyPipe extends NumberPipe implements PipeTransform { export class CurrencyPipe extends NumberPipe implements PipeTransform {
transform(value: any, args: any[]): string { transform(value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD'; digits: string = null): string {
var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false;
var digits: string = isPresent(args) && args.length > 2 ? args[2] : null;
return NumberPipe._format(value, NumberFormatStyle.Currency, digits, currencyCode, return NumberPipe._format(value, NumberFormatStyle.Currency, digits, currencyCode,
symbolDisplay); symbolDisplay);
} }

View File

@ -6,7 +6,6 @@ import {
RegExpWrapper, RegExpWrapper,
StringWrapper StringWrapper
} from 'angular2/src/facade/lang'; } from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {Injectable, PipeTransform, Pipe} from 'angular2/core'; import {Injectable, PipeTransform, Pipe} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@ -39,11 +38,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'replace'}) @Pipe({name: 'replace'})
@Injectable() @Injectable()
export class ReplacePipe implements PipeTransform { export class ReplacePipe implements PipeTransform {
transform(value: any, args: any[]): any { transform(value: any, pattern: string | RegExp, replacement: Function | string): any {
if (isBlank(args) || args.length !== 2) {
throw new BaseException('ReplacePipe requires two arguments');
}
if (isBlank(value)) { if (isBlank(value)) {
return value; return value;
} }
@ -53,9 +48,6 @@ export class ReplacePipe implements PipeTransform {
} }
var input = value.toString(); var input = value.toString();
var pattern = args[0];
var replacement = args[1];
if (!this._supportedPattern(pattern)) { if (!this._supportedPattern(pattern)) {
throw new InvalidPipeArgumentException(ReplacePipe, pattern); throw new InvalidPipeArgumentException(ReplacePipe, pattern);
@ -67,16 +59,16 @@ export class ReplacePipe implements PipeTransform {
// var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern); // var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern);
if (isFunction(replacement)) { if (isFunction(replacement)) {
var rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern; var rgxPattern = isString(pattern) ? RegExpWrapper.create(<string>pattern) : <RegExp>pattern;
return StringWrapper.replaceAllMapped(input, rgxPattern, replacement); return StringWrapper.replaceAllMapped(input, rgxPattern, <Function>replacement);
} }
if (pattern instanceof RegExp) { if (pattern instanceof RegExp) {
// use the replaceAll variant // use the replaceAll variant
return StringWrapper.replaceAll(input, pattern, replacement); return StringWrapper.replaceAll(input, pattern, <string>replacement);
} }
return StringWrapper.replace(input, pattern, replacement); return StringWrapper.replace(input, <string>pattern, <string>replacement);
} }
private _supportedInput(input: any): boolean { return isString(input) || isNumber(input); } private _supportedInput(input: any): boolean { return isString(input) || isNumber(input); }

View File

@ -1,5 +1,4 @@
import {isBlank, isString, isArray, StringWrapper, CONST} from 'angular2/src/facade/lang'; import {isBlank, isString, isArray, StringWrapper, CONST} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import {ListWrapper} from 'angular2/src/facade/collection'; import {ListWrapper} from 'angular2/src/facade/collection';
import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core'; import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@ -59,16 +58,11 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'slice', pure: false}) @Pipe({name: 'slice', pure: false})
@Injectable() @Injectable()
export class SlicePipe implements PipeTransform { export class SlicePipe implements PipeTransform {
transform(value: any, args: any[] = null): any { transform(value: any, start: number, end: number = null): any {
if (isBlank(args) || args.length == 0) {
throw new BaseException('Slice pipe requires one argument');
}
if (!this.supports(value)) { if (!this.supports(value)) {
throw new InvalidPipeArgumentException(SlicePipe, value); throw new InvalidPipeArgumentException(SlicePipe, value);
} }
if (isBlank(value)) return value; if (isBlank(value)) return value;
var start: number = args[0];
var end: number = args.length > 1 ? args[1] : null;
if (isString(value)) { if (isString(value)) {
return StringWrapper.slice(value, start, end); return StringWrapper.slice(value, start, end);
} }

View File

@ -13,7 +13,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
@Pipe({name: 'uppercase'}) @Pipe({name: 'uppercase'})
@Injectable() @Injectable()
export class UpperCasePipe implements PipeTransform { export class UpperCasePipe implements PipeTransform {
transform(value: string, args: any[] = null): string { transform(value: string): string {
if (isBlank(value)) return value; if (isBlank(value)) return value;
if (!isString(value)) { if (!isString(value)) {
throw new InvalidPipeArgumentException(UpperCasePipe, value); throw new InvalidPipeArgumentException(UpperCasePipe, value);

View File

@ -5,7 +5,18 @@ import {
ViewUtils, ViewUtils,
flattenNestedViewRenderNodes, flattenNestedViewRenderNodes,
interpolate, interpolate,
checkBinding checkBinding,
castByValue,
pureProxy1,
pureProxy2,
pureProxy3,
pureProxy4,
pureProxy5,
pureProxy6,
pureProxy7,
pureProxy8,
pureProxy9,
pureProxy10
} from 'angular2/src/core/linker/view_utils'; } from 'angular2/src/core/linker/view_utils';
import { import {
uninitialized, uninitialized,
@ -59,6 +70,7 @@ var impFlattenNestedViewRenderNodes = flattenNestedViewRenderNodes;
var impDevModeEqual = devModeEqual; var impDevModeEqual = devModeEqual;
var impInterpolate = interpolate; var impInterpolate = interpolate;
var impCheckBinding = checkBinding; var impCheckBinding = checkBinding;
var impCastByValue = castByValue;
export class Identifiers { export class Identifiers {
static ViewUtils = new CompileIdentifierMetadata({ static ViewUtils = new CompileIdentifierMetadata({
@ -162,6 +174,31 @@ export class Identifiers {
{name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: impDevModeEqual}); {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: impDevModeEqual});
static interpolate = new CompileIdentifierMetadata( static interpolate = new CompileIdentifierMetadata(
{name: 'interpolate', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impInterpolate}); {name: 'interpolate', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impInterpolate});
static castByValue = new CompileIdentifierMetadata(
{name: 'castByValue', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: impCastByValue});
static pureProxies = [
null,
new CompileIdentifierMetadata(
{name: 'pureProxy1', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy1}),
new CompileIdentifierMetadata(
{name: 'pureProxy2', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy2}),
new CompileIdentifierMetadata(
{name: 'pureProxy3', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy3}),
new CompileIdentifierMetadata(
{name: 'pureProxy4', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy4}),
new CompileIdentifierMetadata(
{name: 'pureProxy5', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy5}),
new CompileIdentifierMetadata(
{name: 'pureProxy6', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy6}),
new CompileIdentifierMetadata(
{name: 'pureProxy7', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy7}),
new CompileIdentifierMetadata(
{name: 'pureProxy8', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy8}),
new CompileIdentifierMetadata(
{name: 'pureProxy9', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy9}),
new CompileIdentifierMetadata(
{name: 'pureProxy10', moduleUrl: VIEW_UTILS_MODULE_URL, runtime: pureProxy10}),
];
} }
export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata { export function identifierToken(identifier: CompileIdentifierMetadata): CompileTokenMetadata {

View File

@ -197,6 +197,11 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex
var name = expr.name; var name = expr.name;
if (isPresent(expr.builtin)) { if (isPresent(expr.builtin)) {
name = this.getBuiltinMethodName(expr.builtin); name = this.getBuiltinMethodName(expr.builtin);
if (isBlank(name)) {
// some builtins just mean to skip the call.
// e.g. `bind` in Dart.
return null;
}
} }
ctx.print(`.${name}(`); ctx.print(`.${name}(`);
this.visitAllExpressions(expr.args, ctx, `,`); this.visitAllExpressions(expr.args, ctx, `,`);

View File

@ -153,6 +153,9 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor {
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
name = 'subscribe'; name = 'subscribe';
break; break;
case o.BuiltinMethod.bind:
name = 'bind';
break;
default: default:
throw new BaseException(`Unknown builtin method: ${method}`); throw new BaseException(`Unknown builtin method: ${method}`);
} }

View File

@ -213,6 +213,9 @@ class _DartEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisito
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
name = 'listen'; name = 'listen';
break; break;
case o.BuiltinMethod.bind:
name = null;
break;
default: default:
throw new BaseException(`Unknown builtin method: ${method}`); throw new BaseException(`Unknown builtin method: ${method}`);
} }

View File

@ -17,8 +17,7 @@ export class InterpretiveAppViewInstanceFactory implements InstanceFactory {
class _InterpretiveAppView extends AppView<any> implements DynamicInstance { class _InterpretiveAppView extends AppView<any> implements DynamicInstance {
constructor(args: any[], public props: Map<string, any>, public getters: Map<string, Function>, constructor(args: any[], public props: Map<string, any>, public getters: Map<string, Function>,
public methods: Map<string, Function>) { public methods: Map<string, Function>) {
super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]);
args[10]);
} }
createInternal(rootSelector: string | any): AppElement { createInternal(rootSelector: string | any): AppElement {
var m = this.methods.get('createInternal'); var m = this.methods.get('createInternal');

View File

@ -244,7 +244,8 @@ export class WritePropExpr extends Expression {
export enum BuiltinMethod { export enum BuiltinMethod {
ConcatArray, ConcatArray,
SubscribeObservable SubscribeObservable,
bind
} }
export class InvokeMethodExpr extends Expression { export class InvokeMethodExpr extends Expression {

View File

@ -187,6 +187,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
result = ObservableWrapper.subscribe(receiver, args[0]); result = ObservableWrapper.subscribe(receiver, args[0]);
break; break;
case o.BuiltinMethod.bind:
if (IS_DART) {
result = receiver;
} else {
result = receiver.bind(args[0]);
}
break;
default: default:
throw new BaseException(`Unknown builtin method ${expr.builtin}`); throw new BaseException(`Unknown builtin method ${expr.builtin}`);
} }
@ -331,6 +338,8 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor {
result = di.props.get(ast.name); result = di.props.get(ast.name);
} else if (di.getters.has(ast.name)) { } else if (di.getters.has(ast.name)) {
result = di.getters.get(ast.name)(); result = di.getters.get(ast.name)();
} else if (di.methods.has(ast.name)) {
result = di.methods.get(ast.name);
} else { } else {
result = reflector.getter(ast.name)(receiver); result = reflector.getter(ast.name)(receiver);
} }

View File

@ -287,6 +287,9 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor
case o.BuiltinMethod.SubscribeObservable: case o.BuiltinMethod.SubscribeObservable:
name = 'subscribe'; name = 'subscribe';
break; break;
case o.BuiltinMethod.bind:
name = 'bind';
break;
default: default:
throw new BaseException(`Unknown builtin method: ${method}`); throw new BaseException(`Unknown builtin method: ${method}`);
} }

View File

@ -324,7 +324,6 @@ export class CompileElement extends CompileNode {
private _getDependency(requestingProviderType: ProviderAstType, private _getDependency(requestingProviderType: ProviderAstType,
dep: CompileDiDependencyMetadata): o.Expression { dep: CompileDiDependencyMetadata): o.Expression {
var currElement: CompileElement = this; var currElement: CompileElement = this;
var currView = currElement.view;
var result = null; var result = null;
if (dep.isValue) { if (dep.isValue) {
result = o.literal(dep.value); result = o.literal(dep.value);
@ -332,14 +331,9 @@ export class CompileElement extends CompileNode {
if (isBlank(result) && !dep.isSkipSelf) { if (isBlank(result) && !dep.isSkipSelf) {
result = this._getLocalDependency(requestingProviderType, dep); result = this._getLocalDependency(requestingProviderType, dep);
} }
var resultViewPath = [];
// check parent elements // check parent elements
while (isBlank(result) && !currElement.parent.isNull()) { while (isBlank(result) && !currElement.parent.isNull()) {
currElement = currElement.parent; currElement = currElement.parent;
while (currElement.view !== currView && currView != null) {
currView = currView.declarationElement.view;
resultViewPath.push(currView);
}
result = currElement._getLocalDependency(ProviderAstType.PublicService, result = currElement._getLocalDependency(ProviderAstType.PublicService,
new CompileDiDependencyMetadata({token: dep.token})); new CompileDiDependencyMetadata({token: dep.token}));
} }
@ -350,7 +344,7 @@ export class CompileElement extends CompileNode {
if (isBlank(result)) { if (isBlank(result)) {
result = o.NULL_EXPR; result = o.NULL_EXPR;
} }
return getPropertyInView(result, resultViewPath); return getPropertyInView(result, this.view, currElement.view);
} }
} }

View File

@ -0,0 +1,76 @@
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from '../output/output_ast';
import {CompileView} from './compile_view';
import {CompilePipeMetadata} from '../compile_metadata';
import {Identifiers, identifierToken} from '../identifiers';
import {injectFromViewParentInjector, createPureProxy, getPropertyInView} from './util';
class _PurePipeProxy {
constructor(public instance: o.ReadPropExpr, public argCount: number) {}
}
export class CompilePipe {
meta: CompilePipeMetadata;
instance: o.ReadPropExpr;
private _purePipeProxies: _PurePipeProxy[] = [];
constructor(public view: CompileView, name: string) {
this.meta = _findPipeMeta(view, name);
this.instance = o.THIS_EXPR.prop(`_pipe_${name}_${view.pipeCount++}`);
}
get pure(): boolean { return this.meta.pure; }
create(): void {
var deps = this.meta.type.diDeps.map((diDep) => {
if (diDep.token.equalsTo(identifierToken(Identifiers.ChangeDetectorRef))) {
return o.THIS_EXPR.prop('ref');
}
return injectFromViewParentInjector(diDep.token, false);
});
this.view.fields.push(new o.ClassField(this.instance.name, o.importType(this.meta.type),
[o.StmtModifier.Private]));
this.view.createMethod.resetDebugInfo(null, null);
this.view.createMethod.addStmt(o.THIS_EXPR.prop(this.instance.name)
.set(o.importExpr(this.meta.type).instantiate(deps))
.toStmt());
this._purePipeProxies.forEach((purePipeProxy) => {
createPureProxy(
this.instance.prop('transform').callMethod(o.BuiltinMethod.bind, [this.instance]),
purePipeProxy.argCount, purePipeProxy.instance, this.view);
});
}
call(callingView: CompileView, args: o.Expression[]): o.Expression {
if (this.meta.pure) {
var purePipeProxy = new _PurePipeProxy(
o.THIS_EXPR.prop(`${this.instance.name}_${this._purePipeProxies.length}`), args.length);
this._purePipeProxies.push(purePipeProxy);
return getPropertyInView(
o.importExpr(Identifiers.castByValue)
.callFn([purePipeProxy.instance, this.instance.prop('transform')]),
callingView, this.view)
.callFn(args);
} else {
return getPropertyInView(this.instance, callingView, this.view).callMethod('transform', args);
}
}
}
function _findPipeMeta(view: CompileView, name: string): CompilePipeMetadata {
var pipeMeta: CompilePipeMetadata = null;
for (var i = view.pipeMetas.length - 1; i >= 0; i--) {
var localPipeMeta = view.pipeMetas[i];
if (localPipeMeta.name == name) {
pipeMeta = localPipeMeta;
break;
}
}
if (isBlank(pipeMeta)) {
throw new BaseException(
`Illegal state: Could not find pipe ${name} although the parser should have detected this error!`);
}
return pipeMeta;
}

View File

@ -30,14 +30,12 @@ export class CompileQuery {
addValue(value: o.Expression, view: CompileView) { addValue(value: o.Expression, view: CompileView) {
var currentView = view; var currentView = view;
var elPath: CompileElement[] = []; var elPath: CompileElement[] = [];
var viewPath: CompileView[] = [];
while (isPresent(currentView) && currentView !== this.view) { while (isPresent(currentView) && currentView !== this.view) {
var parentEl = currentView.declarationElement; var parentEl = currentView.declarationElement;
elPath.unshift(parentEl); elPath.unshift(parentEl);
currentView = parentEl.view; currentView = parentEl.view;
viewPath.push(currentView);
} }
var queryListForDirtyExpr = getPropertyInView(this.queryList, viewPath); var queryListForDirtyExpr = getPropertyInView(this.queryList, view, this.view);
var viewValues = this._values; var viewValues = this._values;
elPath.forEach((el) => { elPath.forEach((el) => {

View File

@ -1,14 +1,13 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, StringMapWrapper, MapWrapper} from 'angular2/src/facade/collection';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import {Identifiers, identifierToken} from '../identifiers';
import {EventHandlerVars} from './constants'; import {EventHandlerVars} from './constants';
import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query'; import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query';
import {NameResolver} from './expression_converter'; import {NameResolver} from './expression_converter';
import {CompileElement, CompileNode} from './compile_element'; import {CompileElement, CompileNode} from './compile_element';
import {CompileMethod} from './compile_method'; import {CompileMethod} from './compile_method';
import {CompilePipe} from './compile_pipe';
import {ViewType} from 'angular2/src/core/linker/view_type'; import {ViewType} from 'angular2/src/core/linker/view_type';
import { import {
CompileDirectiveMetadata, CompileDirectiveMetadata,
@ -20,17 +19,12 @@ import {
getViewFactoryName, getViewFactoryName,
injectFromViewParentInjector, injectFromViewParentInjector,
createDiTokenExpression, createDiTokenExpression,
getPropertyInView getPropertyInView,
createPureProxy
} from './util'; } from './util';
import {CompilerConfig} from '../config'; import {CompilerConfig} from '../config';
import {CompileBinding} from './compile_binding'; import {CompileBinding} from './compile_binding';
import {bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder';
export class CompilePipe {
constructor() {}
}
export class CompileView implements NameResolver { export class CompileView implements NameResolver {
public viewType: ViewType; public viewType: ViewType;
public viewQueries: CompileTokenMap<CompileQuery[]>; public viewQueries: CompileTokenMap<CompileQuery[]>;
@ -60,7 +54,8 @@ export class CompileView implements NameResolver {
public subscriptions: o.Expression[] = []; public subscriptions: o.Expression[] = [];
public componentView: CompileView; public componentView: CompileView;
public pipes = new Map<string, o.Expression>(); public purePipes = new Map<string, CompilePipe>();
public pipes: CompilePipe[] = [];
public variables = new Map<string, o.Expression>(); public variables = new Map<string, o.Expression>();
public className: string; public className: string;
public classType: o.Type; public classType: o.Type;
@ -68,6 +63,7 @@ export class CompileView implements NameResolver {
public literalArrayCount = 0; public literalArrayCount = 0;
public literalMapCount = 0; public literalMapCount = 0;
public pipeCount = 0;
constructor(public component: CompileDirectiveMetadata, public genConfig: CompilerConfig, constructor(public component: CompileDirectiveMetadata, public genConfig: CompilerConfig,
public pipeMetas: CompilePipeMetadata[], public styles: o.Expression, public pipeMetas: CompilePipeMetadata[], public styles: o.Expression,
@ -124,39 +120,17 @@ export class CompileView implements NameResolver {
} }
} }
createPipe(name: string): o.Expression { callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression {
var pipeMeta: CompilePipeMetadata = null; var compView = this.componentView;
for (var i = this.pipeMetas.length - 1; i >= 0; i--) { var pipe = compView.purePipes.get(name);
var localPipeMeta = this.pipeMetas[i]; if (isBlank(pipe)) {
if (localPipeMeta.name == name) { pipe = new CompilePipe(compView, name);
pipeMeta = localPipeMeta; if (pipe.pure) {
break; compView.purePipes.set(name, pipe);
} }
compView.pipes.push(pipe);
} }
if (isBlank(pipeMeta)) { return pipe.call(this, [input].concat(args));
throw new BaseException(
`Illegal state: Could not find pipe ${name} although the parser should have detected this error!`);
}
var pipeFieldName = pipeMeta.pure ? `_pipe_${name}` : `_pipe_${name}_${this.pipes.size}`;
var pipeExpr = this.pipes.get(pipeFieldName);
if (isBlank(pipeExpr)) {
var deps = pipeMeta.type.diDeps.map((diDep) => {
if (diDep.token.equalsTo(identifierToken(Identifiers.ChangeDetectorRef))) {
return o.THIS_EXPR.prop('ref');
}
return injectFromViewParentInjector(diDep.token, false);
});
this.fields.push(
new o.ClassField(pipeFieldName, o.importType(pipeMeta.type), [o.StmtModifier.Private]));
this.createMethod.resetDebugInfo(null, null);
this.createMethod.addStmt(o.THIS_EXPR.prop(pipeFieldName)
.set(o.importExpr(pipeMeta.type).instantiate(deps))
.toStmt());
pipeExpr = o.THIS_EXPR.prop(pipeFieldName);
this.pipes.set(pipeFieldName, pipeExpr);
bindPipeDestroyLifecycleCallbacks(pipeMeta, pipeExpr, this);
}
return pipeExpr;
} }
getVariable(name: string): o.Expression { getVariable(name: string): o.Expression {
@ -165,29 +139,49 @@ export class CompileView implements NameResolver {
} }
var currView: CompileView = this; var currView: CompileView = this;
var result = currView.variables.get(name); var result = currView.variables.get(name);
var viewPath = [];
while (isBlank(result) && isPresent(currView.declarationElement.view)) { while (isBlank(result) && isPresent(currView.declarationElement.view)) {
currView = currView.declarationElement.view; currView = currView.declarationElement.view;
result = currView.variables.get(name); result = currView.variables.get(name);
viewPath.push(currView);
} }
if (isPresent(result)) { if (isPresent(result)) {
return getPropertyInView(result, viewPath); return getPropertyInView(result, this, currView);
} else { } else {
return null; return null;
} }
} }
createLiteralArray(values: o.Expression[]): o.Expression { createLiteralArray(values: o.Expression[]): o.Expression {
return o.THIS_EXPR.callMethod('literalArray', var proxyExpr = o.THIS_EXPR.prop(`_arr_${this.literalArrayCount++}`);
[o.literal(this.literalArrayCount++), o.literalArr(values)]); var proxyParams: o.FnParam[] = [];
var proxyReturnEntries: o.Expression[] = [];
for (var i = 0; i < values.length; i++) {
var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push(o.variable(paramName));
} }
createLiteralMap(values: Array<Array<string | o.Expression>>): o.Expression { createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))]),
return o.THIS_EXPR.callMethod('literalMap', values.length, proxyExpr, this);
[o.literal(this.literalMapCount++), o.literalMap(values)]); return proxyExpr.callFn(values);
}
createLiteralMap(entries: Array<Array<string | o.Expression>>): o.Expression {
var proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`);
var proxyParams: o.FnParam[] = [];
var proxyReturnEntries: Array<Array<string | o.Expression>> = [];
var values: o.Expression[] = [];
for (var i = 0; i < entries.length; i++) {
var paramName = `p${i}`;
proxyParams.push(new o.FnParam(paramName));
proxyReturnEntries.push([entries[i][0], o.variable(paramName)]);
values.push(<o.Expression>entries[i][1]);
}
createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))]),
entries.length, proxyExpr, this);
return proxyExpr.callFn(values);
} }
afterNodes() { afterNodes() {
this.pipes.forEach((pipe) => pipe.create());
this.viewQueries.values().forEach( this.viewQueries.values().forEach(
(queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod))); (queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod)));
} }

View File

@ -8,7 +8,7 @@ import {isBlank, isPresent, isArray, CONST_EXPR} from 'angular2/src/facade/lang'
var IMPLICIT_RECEIVER = o.variable('#implicit'); var IMPLICIT_RECEIVER = o.variable('#implicit');
export interface NameResolver { export interface NameResolver {
createPipe(name: string): o.Expression; callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression;
getVariable(name: string): o.Expression; getVariable(name: string): o.Expression;
createLiteralArray(values: o.Expression[]): o.Expression; createLiteralArray(values: o.Expression[]): o.Expression;
createLiteralMap(values: Array<Array<string | o.Expression>>): o.Expression; createLiteralMap(values: Array<Array<string | o.Expression>>): o.Expression;
@ -132,13 +132,11 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
ast.falseExp.visit(this, _Mode.Expression))); ast.falseExp.visit(this, _Mode.Expression)));
} }
visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any { visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any {
var pipeInstance = this._nameResolver.createPipe(ast.name);
var input = ast.exp.visit(this, _Mode.Expression); var input = ast.exp.visit(this, _Mode.Expression);
var args = this.visitAll(ast.args, _Mode.Expression); var args = this.visitAll(ast.args, _Mode.Expression);
var value = this._nameResolver.callPipe(ast.name, input, args);
this.needsValueUnwrapper = true; this.needsValueUnwrapper = true;
return convertToStatementIfNeeded( return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value]));
mode, this._valueUnwrapper.callMethod(
'unwrap', [pipeInstance.callMethod('transform', [input, o.literalArr(args)])]));
} }
visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any {
return convertToStatementIfNeeded(mode, ast.target.visit(this, _Mode.Expression) return convertToStatementIfNeeded(mode, ast.target.visit(this, _Mode.Expression)

View File

@ -78,10 +78,10 @@ export function bindDirectiveDestroyLifecycleCallbacks(directiveMeta: CompileDir
} }
} }
export function bindPipeDestroyLifecycleCallbacks( export function bindPipeDestroyLifecycleCallbacks(pipeMeta: CompilePipeMetadata,
pipeMeta: CompilePipeMetadata, directiveInstance: o.Expression, view: CompileView) { pipeInstance: o.Expression, view: CompileView) {
var onDestroyMethod = view.destroyMethod; var onDestroyMethod = view.destroyMethod;
if (pipeMeta.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) { if (pipeMeta.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) {
onDestroyMethod.addStmt(directiveInstance.callMethod('ngOnDestroy', []).toStmt()); onDestroyMethod.addStmt(pipeInstance.callMethod('ngOnDestroy', []).toStmt());
} }
} }

View File

@ -1,4 +1,5 @@
import {isPresent, isBlank} from 'angular2/src/facade/lang'; import {isPresent, isBlank} from 'angular2/src/facade/lang';
import {BaseException} from 'angular2/src/facade/exceptions';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
import { import {
@ -7,22 +8,29 @@ import {
CompileIdentifierMetadata CompileIdentifierMetadata
} from '../compile_metadata'; } from '../compile_metadata';
import {CompileView} from './compile_view'; import {CompileView} from './compile_view';
import {Identifiers} from '../identifiers';
export function getPropertyInView(property: o.Expression, viewPath: CompileView[]): o.Expression { export function getPropertyInView(property: o.Expression, callingView: CompileView,
if (viewPath.length === 0) { definedView: CompileView): o.Expression {
if (callingView === definedView) {
return property; return property;
} else { } else {
var viewProp: o.Expression = o.THIS_EXPR; var viewProp: o.Expression = o.THIS_EXPR;
for (var i = 0; i < viewPath.length; i++) { var currView: CompileView = callingView;
viewProp = viewProp.prop('declarationAppElement').prop('parentView'); while (currView !== definedView && isPresent(currView.declarationElement.view)) {
currView = currView.declarationElement.view;
viewProp = viewProp.prop('parent');
}
if (currView !== definedView) {
throw new BaseException(
`Internal error: Could not calculate a property in a parent view: ${property}`);
} }
if (property instanceof o.ReadPropExpr) { if (property instanceof o.ReadPropExpr) {
var lastView = viewPath[viewPath.length - 1];
let readPropExpr: o.ReadPropExpr = property; let readPropExpr: o.ReadPropExpr = property;
// Note: Don't cast for members of the AppView base class... // Note: Don't cast for members of the AppView base class...
if (lastView.fields.some((field) => field.name == readPropExpr.name) || if (definedView.fields.some((field) => field.name == readPropExpr.name) ||
lastView.getters.some((field) => field.name == readPropExpr.name)) { definedView.getters.some((field) => field.name == readPropExpr.name)) {
viewProp = viewProp.cast(lastView.classType); viewProp = viewProp.cast(definedView.classType);
} }
} }
return o.replaceVarInExpression(o.THIS_EXPR.name, viewProp, property); return o.replaceVarInExpression(o.THIS_EXPR.name, viewProp, property);
@ -77,3 +85,15 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression {
} }
return result; return result;
} }
export function createPureProxy(fn: o.Expression, argCount: number, pureProxyProp: o.ReadPropExpr,
view: CompileView) {
view.fields.push(new o.ClassField(pureProxyProp.name, null, [o.StmtModifier.Private]));
var pureProxyId =
argCount < Identifiers.pureProxies.length ? Identifiers.pureProxies[argCount] : null;
if (isBlank(pureProxyId)) {
throw new BaseException(`Unsupported number of argument for pure functions: ${argCount}`);
}
view.createMethod.addStmt(
o.THIS_EXPR.prop(pureProxyProp.name).set(o.importExpr(pureProxyId).callFn([fn])).toStmt());
}

View File

@ -39,6 +39,8 @@ import {CompileElement, CompileNode} from './compile_element';
export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void { export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void {
var visitor = new ViewBinderVisitor(view); var visitor = new ViewBinderVisitor(view);
templateVisitAll(visitor, parsedTemplate); templateVisitAll(visitor, parsedTemplate);
view.pipes.forEach(
(pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); });
} }
class ViewBinderVisitor implements TemplateAstVisitor { class ViewBinderVisitor implements TemplateAstVisitor {

View File

@ -1,4 +1,4 @@
import {isPresent, StringWrapper} from 'angular2/src/facade/lang'; import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
import {ListWrapper, StringMapWrapper, SetWrapper} from 'angular2/src/facade/collection'; import {ListWrapper, StringMapWrapper, SetWrapper} from 'angular2/src/facade/collection';
import * as o from '../output/output_ast'; import * as o from '../output/output_ast';
@ -429,8 +429,6 @@ function createViewClass(view: CompileView, renderCompTypeVar: o.ReadVarExpr,
ViewConstructorVars.parentInjector, ViewConstructorVars.parentInjector,
ViewConstructorVars.declarationEl, ViewConstructorVars.declarationEl,
ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view)), ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view)),
o.literal(view.literalArrayCount),
o.literal(view.literalMapCount),
nodeDebugInfosVar nodeDebugInfosVar
]) ])
.toStmt() .toStmt()

View File

@ -0,0 +1,33 @@
/**
* To create a Pipe, you must implement this interface.
*
* Angular invokes the `transform` method with the value of a binding
* as the first argument, and any parameters as the second argument in list form.
*
* ## Syntax
*
* `value | pipeName[:arg0[:arg1...]]`
*
* ### Example ([live demo](http://plnkr.co/edit/f5oyIked9M2cKzvZNKHV?p=preview))
*
* The `RepeatPipe` below repeats the value as many times as indicated by the first argument:
*
* ```
* import {Pipe, PipeTransform} from 'angular2/core';
*
* @Pipe({name: 'repeat'})
* export class RepeatPipe implements PipeTransform {
* transform(value: any, times: number) {
* return value.repeat(times);
* }
* }
* ```
*
* Invoking `{{ 'ok' | repeat:3 }}` in a template produces `okokok`.
*
*/
abstract class PipeTransform {
// Note: Dart does not support varargs,
// so we can't type the `transform` method...
// dynamic transform(dynamic value, List<dynamic> ...args): any;
}

View File

@ -17,11 +17,7 @@
* *
* @Pipe({name: 'repeat'}) * @Pipe({name: 'repeat'})
* export class RepeatPipe implements PipeTransform { * export class RepeatPipe implements PipeTransform {
* transform(value: any, args: any[] = []) { * transform(value: any, times: number) {
* if (args.length == 0) {
* throw new Error('repeat pipe requires one argument');
* }
* let times: number = args[0];
* return value.repeat(times); * return value.repeat(times);
* } * }
* } * }
@ -30,4 +26,4 @@
* Invoking `{{ 'ok' | repeat:3 }}` in a template produces `okokok`. * Invoking `{{ 'ok' | repeat:3 }}` in a template produces `okokok`.
* *
*/ */
export interface PipeTransform { transform(value: any, args: any[]): any; } export interface PipeTransform { transform(value: any, ...args: any[]): any; }

View File

@ -70,9 +70,6 @@ export abstract class AppView<T> {
renderParent: AppView<any>; renderParent: AppView<any>;
viewContainerElement: AppElement = null; viewContainerElement: AppElement = null;
private _literalArrayCache: any[][];
private _literalMapCache: Array<{[key: string]: any}>;
// The names of the below fields must be kept in sync with codegen_name_util.ts or // The names of the below fields must be kept in sync with codegen_name_util.ts or
// change detection will fail. // change detection will fail.
cdState: ChangeDetectorState = ChangeDetectorState.NeverChecked; cdState: ChangeDetectorState = ChangeDetectorState.NeverChecked;
@ -96,16 +93,14 @@ export abstract class AppView<T> {
constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType, constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType,
public locals: {[key: string]: any}, public viewUtils: ViewUtils, public locals: {[key: string]: any}, public viewUtils: ViewUtils,
public parentInjector: Injector, public declarationAppElement: AppElement, public parentInjector: Injector, public declarationAppElement: AppElement,
public cdMode: ChangeDetectionStrategy, literalArrayCacheSize: number, public cdMode: ChangeDetectionStrategy,
literalMapCacheSize: number, public staticNodeDebugInfos: StaticNodeDebugInfo[]) { public staticNodeDebugInfos: StaticNodeDebugInfo[]) {
this.ref = new ViewRef_(this); this.ref = new ViewRef_(this);
if (type === ViewType.COMPONENT || type === ViewType.HOST) { if (type === ViewType.COMPONENT || type === ViewType.HOST) {
this.renderer = viewUtils.renderComponent(componentType); this.renderer = viewUtils.renderComponent(componentType);
} else { } else {
this.renderer = declarationAppElement.parentView.renderer; this.renderer = declarationAppElement.parentView.renderer;
} }
this._literalArrayCache = ListWrapper.createFixedSize(literalArrayCacheSize);
this._literalMapCache = ListWrapper.createFixedSize(literalMapCacheSize);
} }
create(givenProjectableNodes: Array<any | any[]>, rootSelectorOrNode: string | any): AppElement { create(givenProjectableNodes: Array<any | any[]>, rootSelectorOrNode: string | any): AppElement {
@ -269,6 +264,10 @@ export abstract class AppView<T> {
get changeDetectorRef(): ChangeDetectorRef { return this.ref; } get changeDetectorRef(): ChangeDetectorRef { return this.ref; }
get parent(): AppView<any> {
return isPresent(this.declarationAppElement) ? this.declarationAppElement.parentView : null;
}
get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); } get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); }
get lastRootNode(): any { get lastRootNode(): any {
@ -360,28 +359,6 @@ export abstract class AppView<T> {
this.viewContainerElement = null; this.viewContainerElement = null;
} }
literalArray(id: number, value: any[]): any[] {
var prevValue = this._literalArrayCache[id];
if (isBlank(value)) {
return value;
}
if (isBlank(prevValue) || !arrayLooseIdentical(prevValue, value)) {
prevValue = this._literalArrayCache[id] = value;
}
return prevValue;
}
literalMap(id: number, value: {[key: string]: any}): {[key: string]: any} {
var prevValue = this._literalMapCache[id];
if (isBlank(value)) {
return value;
}
if (isBlank(prevValue) || !mapLooseIdentical(prevValue, value)) {
prevValue = this._literalMapCache[id] = value;
}
return prevValue;
}
markAsCheckOnce(): void { this.cdMode = ChangeDetectionStrategy.CheckOnce; } markAsCheckOnce(): void { this.cdMode = ChangeDetectionStrategy.CheckOnce; }
markPathToRootAsCheckOnce(): void { markPathToRootAsCheckOnce(): void {

View File

@ -10,7 +10,7 @@ import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {BaseException} from 'angular2/src/facade/exceptions'; import {BaseException} from 'angular2/src/facade/exceptions';
import {AppElement} from './element'; import {AppElement} from './element';
import {ExpressionChangedAfterItHasBeenCheckedException} from './exceptions'; import {ExpressionChangedAfterItHasBeenCheckedException} from './exceptions';
import {devModeEqual} from 'angular2/src/core/change_detection/change_detection'; import {devModeEqual, uninitialized} from 'angular2/src/core/change_detection/change_detection';
import {Inject, Injectable} from 'angular2/src/core/di'; import {Inject, Injectable} from 'angular2/src/core/di';
import {RootRenderer, RenderComponentType, Renderer} from 'angular2/src/core/render/api'; import {RootRenderer, RenderComponentType, Renderer} from 'angular2/src/core/render/api';
import {APP_ID} from 'angular2/src/core/application_tokens'; import {APP_ID} from 'angular2/src/core/application_tokens';
@ -158,3 +158,209 @@ export function mapLooseIdentical<V>(m1: {[key: string]: V}, m2: {[key: string]:
} }
return true; return true;
} }
export function castByValue<T>(input: any, value: T): T {
return <T>input;
}
export function pureProxy1<P0, R>(fn: (p0: P0) => R): (p0: P0) => R {
var result: R;
var v0;
v0 = uninitialized;
return (p0) => {
if (!looseIdentical(v0, p0)) {
v0 = p0;
result = fn(p0);
}
return result;
};
}
export function pureProxy2<P0, P1, R>(fn: (p0: P0, p1: P1) => R): (p0: P0, p1: P1) => R {
var result: R;
var v0, v1;
v0 = v1 = uninitialized;
return (p0, p1) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1)) {
v0 = p0;
v1 = p1;
result = fn(p0, p1);
}
return result;
};
}
export function pureProxy3<P0, P1, P2, R>(fn: (p0: P0, p1: P1, p2: P2) => R): (p0: P0, p1: P1,
p2: P2) => R {
var result: R;
var v0, v1, v2;
v0 = v1 = v2 = uninitialized;
return (p0, p1, p2) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2)) {
v0 = p0;
v1 = p1;
v2 = p2;
result = fn(p0, p1, p2);
}
return result;
};
}
export function pureProxy4<P0, P1, P2, P3, R>(fn: (p0: P0, p1: P1, p2: P2, p3: P3) => R): (
p0: P0, p1: P1, p2: P2, p3: P3) => R {
var result: R;
var v0, v1, v2, v3;
v0 = v1 = v2 = v3 = uninitialized;
return (p0, p1, p2, p3) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
result = fn(p0, p1, p2, p3);
}
return result;
};
}
export function pureProxy5<P0, P1, P2, P3, P4, R>(
fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) => R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) =>
R {
var result: R;
var v0, v1, v2, v3, v4;
v0 = v1 = v2 = v3 = v4 = uninitialized;
return (p0, p1, p2, p3, p4) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3) || !looseIdentical(v4, p4)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
result = fn(p0, p1, p2, p3, p4);
}
return result;
};
}
export function pureProxy6<P0, P1, P2, P3, P4, P5, R>(
fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5) => R): (p0: P0, p1: P1, p2: P2, p3: P3,
p4: P4, p5: P5) => R {
var result: R;
var v0, v1, v2, v3, v4, v5;
v0 = v1 = v2 = v3 = v4 = v5 = uninitialized;
return (p0, p1, p2, p3, p4, p5) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
v5 = p5;
result = fn(p0, p1, p2, p3, p4, p5);
}
return result;
};
}
export function pureProxy7<P0, P1, P2, P3, P4, P5, P6, R>(
fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) =>
R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6) => R {
var result: R;
var v0, v1, v2, v3, v4, v5, v6;
v0 = v1 = v2 = v3 = v4 = v5 = v6 = uninitialized;
return (p0, p1, p2, p3, p4, p5, p6) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) ||
!looseIdentical(v6, p6)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
v5 = p5;
v6 = p6;
result = fn(p0, p1, p2, p3, p4, p5, p6);
}
return result;
};
}
export function pureProxy8<P0, P1, P2, P3, P4, P5, P6, P7, R>(
fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) =>
R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7) => R {
var result: R;
var v0, v1, v2, v3, v4, v5, v6, v7;
v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = uninitialized;
return (p0, p1, p2, p3, p4, p5, p6, p7) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) ||
!looseIdentical(v6, p6) || !looseIdentical(v7, p7)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
v5 = p5;
v6 = p6;
v7 = p7;
result = fn(p0, p1, p2, p3, p4, p5, p6, p7);
}
return result;
};
}
export function pureProxy9<P0, P1, P2, P3, P4, P5, P6, P7, P8, R>(
fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) =>
R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8) => R {
var result: R;
var v0, v1, v2, v3, v4, v5, v6, v7, v8;
v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = uninitialized;
return (p0, p1, p2, p3, p4, p5, p6, p7, p8) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) ||
!looseIdentical(v6, p6) || !looseIdentical(v7, p7) || !looseIdentical(v8, p8)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
v5 = p5;
v6 = p6;
v7 = p7;
v8 = p8;
result = fn(p0, p1, p2, p3, p4, p5, p6, p7, p8);
}
return result;
};
}
export function pureProxy10<P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, R>(
fn: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) =>
R): (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9) => R {
var result: R;
var v0, v1, v2, v3, v4, v5, v6, v7, v8, v9;
v0 = v1 = v2 = v3 = v4 = v5 = v6 = v7 = v8 = v9 = uninitialized;
return (p0, p1, p2, p3, p4, p5, p6, p7, p8, p9) => {
if (!looseIdentical(v0, p0) || !looseIdentical(v1, p1) || !looseIdentical(v2, p2) ||
!looseIdentical(v3, p3) || !looseIdentical(v4, p4) || !looseIdentical(v5, p5) ||
!looseIdentical(v6, p6) || !looseIdentical(v7, p7) || !looseIdentical(v8, p8) ||
!looseIdentical(v9, p9)) {
v0 = p0;
v1 = p1;
v2 = p2;
v3 = p3;
v4 = p4;
v5 = p5;
v6 = p6;
v7 = p7;
v8 = p8;
v9 = p9;
result = fn(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
return result;
};
}

View File

@ -208,14 +208,14 @@ export function main() {
describe('null', () => { describe('null', () => {
it('should return null when given null', () => { it('should return null when given null', () => {
var pipe = new AsyncPipe(null); var pipe = new AsyncPipe(null);
expect(pipe.transform(null, [])).toEqual(null); expect(pipe.transform(null)).toEqual(null);
}); });
}); });
describe('other types', () => { describe('other types', () => {
it('should throw when given an invalid object', () => { it('should throw when given an invalid object', () => {
var pipe = new AsyncPipe(null); var pipe = new AsyncPipe(null);
expect(() => pipe.transform(<any>"some bogus object", [])).toThrowError(); expect(() => pipe.transform(<any>"some bogus object")).toThrowError();
}); });
}); });
}); });

View File

@ -42,39 +42,39 @@ export function main() {
if (browserDetection.supportsIntlApi) { if (browserDetection.supportsIntlApi) {
describe("transform", () => { describe("transform", () => {
it('should format each component correctly', () => { it('should format each component correctly', () => {
expect(pipe.transform(date, ['y'])).toEqual('2015'); expect(pipe.transform(date, 'y')).toEqual('2015');
expect(pipe.transform(date, ['yy'])).toEqual('15'); expect(pipe.transform(date, 'yy')).toEqual('15');
expect(pipe.transform(date, ['M'])).toEqual('6'); expect(pipe.transform(date, 'M')).toEqual('6');
expect(pipe.transform(date, ['MM'])).toEqual('06'); expect(pipe.transform(date, 'MM')).toEqual('06');
expect(pipe.transform(date, ['MMM'])).toEqual('Jun'); expect(pipe.transform(date, 'MMM')).toEqual('Jun');
expect(pipe.transform(date, ['MMMM'])).toEqual('June'); expect(pipe.transform(date, 'MMMM')).toEqual('June');
expect(pipe.transform(date, ['d'])).toEqual('15'); expect(pipe.transform(date, 'd')).toEqual('15');
expect(pipe.transform(date, ['E'])).toEqual('Mon'); expect(pipe.transform(date, 'E')).toEqual('Mon');
expect(pipe.transform(date, ['EEEE'])).toEqual('Monday'); expect(pipe.transform(date, 'EEEE')).toEqual('Monday');
expect(pipe.transform(date, ['H'])).toEqual('21'); expect(pipe.transform(date, 'H')).toEqual('21');
expect(pipe.transform(date, ['j'])).toEqual('9 PM'); expect(pipe.transform(date, 'j')).toEqual('9 PM');
expect(pipe.transform(date, ['m'])).toEqual('43'); expect(pipe.transform(date, 'm')).toEqual('43');
expect(pipe.transform(date, ['s'])).toEqual('11'); expect(pipe.transform(date, 's')).toEqual('11');
}); });
it('should format common multi component patterns', () => { it('should format common multi component patterns', () => {
expect(pipe.transform(date, ['yMEd'])).toEqual('Mon, 6/15/2015'); expect(pipe.transform(date, 'yMEd')).toEqual('Mon, 6/15/2015');
expect(pipe.transform(date, ['MEd'])).toEqual('Mon, 6/15'); expect(pipe.transform(date, 'MEd')).toEqual('Mon, 6/15');
expect(pipe.transform(date, ['MMMd'])).toEqual('Jun 15'); expect(pipe.transform(date, 'MMMd')).toEqual('Jun 15');
expect(pipe.transform(date, ['yMMMMEEEEd'])).toEqual('Monday, June 15, 2015'); expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015');
expect(pipe.transform(date, ['jms'])).toEqual('9:43:11 PM'); expect(pipe.transform(date, 'jms')).toEqual('9:43:11 PM');
expect(pipe.transform(date, ['ms'])).toEqual('43:11'); expect(pipe.transform(date, 'ms')).toEqual('43:11');
}); });
it('should format with pattern aliases', () => { it('should format with pattern aliases', () => {
expect(pipe.transform(date, ['medium'])).toEqual('Jun 15, 2015, 9:43:11 PM'); expect(pipe.transform(date, 'medium')).toEqual('Jun 15, 2015, 9:43:11 PM');
expect(pipe.transform(date, ['short'])).toEqual('6/15/2015, 9:43 PM'); expect(pipe.transform(date, 'short')).toEqual('6/15/2015, 9:43 PM');
expect(pipe.transform(date, ['fullDate'])).toEqual('Monday, June 15, 2015'); expect(pipe.transform(date, 'fullDate')).toEqual('Monday, June 15, 2015');
expect(pipe.transform(date, ['longDate'])).toEqual('June 15, 2015'); expect(pipe.transform(date, 'longDate')).toEqual('June 15, 2015');
expect(pipe.transform(date, ['mediumDate'])).toEqual('Jun 15, 2015'); expect(pipe.transform(date, 'mediumDate')).toEqual('Jun 15, 2015');
expect(pipe.transform(date, ['shortDate'])).toEqual('6/15/2015'); expect(pipe.transform(date, 'shortDate')).toEqual('6/15/2015');
expect(pipe.transform(date, ['mediumTime'])).toEqual('9:43:11 PM'); expect(pipe.transform(date, 'mediumTime')).toEqual('9:43:11 PM');
expect(pipe.transform(date, ['shortTime'])).toEqual('9:43 PM'); expect(pipe.transform(date, 'shortTime')).toEqual('9:43 PM');
}); });
}); });
} }

View File

@ -26,33 +26,33 @@ export function main() {
describe("transform", () => { describe("transform", () => {
it("should return 0 text if value is 0", () => { it("should return 0 text if value is 0", () => {
var val = pipe.transform(0, [mapping]); var val = pipe.transform(0, mapping);
expect(val).toEqual('No messages.'); expect(val).toEqual('No messages.');
}); });
it("should return 1 text if value is 1", () => { it("should return 1 text if value is 1", () => {
var val = pipe.transform(1, [mapping]); var val = pipe.transform(1, mapping);
expect(val).toEqual('One message.'); expect(val).toEqual('One message.');
}); });
it("should return other text if value is anything other than 0 or 1", () => { it("should return other text if value is anything other than 0 or 1", () => {
var val = pipe.transform(6, [mapping]); var val = pipe.transform(6, mapping);
expect(val).toEqual('There are some messages.'); expect(val).toEqual('There are some messages.');
}); });
it("should interpolate the value into the text where indicated", () => { it("should interpolate the value into the text where indicated", () => {
var val = pipe.transform(6, [interpolatedMapping]); var val = pipe.transform(6, interpolatedMapping);
expect(val).toEqual('There are 6 messages, that is 6.'); expect(val).toEqual('There are 6 messages, that is 6.');
}); });
it("should use 'other' if value is undefined", () => { it("should use 'other' if value is undefined", () => {
var messageLength; var messageLength;
var val = pipe.transform(messageLength, [interpolatedMapping]); var val = pipe.transform(messageLength, interpolatedMapping);
expect(val).toEqual('There are messages, that is .'); expect(val).toEqual('There are messages, that is .');
}); });
it("should not support bad arguments", it("should not support bad arguments",
() => { expect(() => pipe.transform(0, ['hey'])).toThrowError(); }); () => { expect(() => pipe.transform(0, 'hey')).toThrowError(); });
}); });
}); });

View File

@ -24,28 +24,28 @@ export function main() {
describe("transform", () => { describe("transform", () => {
it("should return male text if value is male", () => { it("should return male text if value is male", () => {
var val = pipe.transform('male', [mapping]); var val = pipe.transform('male', mapping);
expect(val).toEqual('Invite him.'); expect(val).toEqual('Invite him.');
}); });
it("should return female text if value is female", () => { it("should return female text if value is female", () => {
var val = pipe.transform('female', [mapping]); var val = pipe.transform('female', mapping);
expect(val).toEqual('Invite her.'); expect(val).toEqual('Invite her.');
}); });
it("should return other text if value is anything other than male or female", () => { it("should return other text if value is anything other than male or female", () => {
var val = pipe.transform('Anything else', [mapping]); var val = pipe.transform('Anything else', mapping);
expect(val).toEqual('Invite them.'); expect(val).toEqual('Invite them.');
}); });
it("should use 'other' if value is undefined", () => { it("should use 'other' if value is undefined", () => {
var gender; var gender;
var val = pipe.transform(gender, [mapping]); var val = pipe.transform(gender, mapping);
expect(val).toEqual('Invite them.'); expect(val).toEqual('Invite them.');
}); });
it("should not support bad arguments", it("should not support bad arguments",
() => { expect(() => pipe.transform('male', ['hey'])).toThrowError(); }); () => { expect(() => pipe.transform('male', 'hey')).toThrowError(); });
}); });
}); });

View File

@ -24,17 +24,17 @@ export function main() {
describe("transform", () => { describe("transform", () => {
it('should return correct value for numbers', () => { it('should return correct value for numbers', () => {
expect(pipe.transform(12345, [])).toEqual('12,345'); expect(pipe.transform(12345)).toEqual('12,345');
expect(pipe.transform(123, ['.2'])).toEqual('123.00'); expect(pipe.transform(123, '.2')).toEqual('123.00');
expect(pipe.transform(1, ['3.'])).toEqual('001'); expect(pipe.transform(1, '3.')).toEqual('001');
expect(pipe.transform(1.1, ['3.4-5'])).toEqual('001.1000'); expect(pipe.transform(1.1, '3.4-5')).toEqual('001.1000');
expect(pipe.transform(1.123456, ['3.4-5'])).toEqual('001.12346'); expect(pipe.transform(1.123456, '3.4-5')).toEqual('001.12346');
expect(pipe.transform(1.1234, [])).toEqual('1.123'); expect(pipe.transform(1.1234)).toEqual('1.123');
}); });
it("should not support other objects", it("should not support other objects",
() => { expect(() => pipe.transform(new Object(), [])).toThrowError(); }); () => { expect(() => pipe.transform(new Object())).toThrowError(); });
}); });
}); });
@ -45,12 +45,12 @@ export function main() {
describe("transform", () => { describe("transform", () => {
it('should return correct value for numbers', () => { it('should return correct value for numbers', () => {
expect(pipe.transform(1.23, [])).toEqual('123%'); expect(pipe.transform(1.23)).toEqual('123%');
expect(pipe.transform(1.2, ['.2'])).toEqual('120.00%'); expect(pipe.transform(1.2, '.2')).toEqual('120.00%');
}); });
it("should not support other objects", it("should not support other objects",
() => { expect(() => pipe.transform(new Object(), [])).toThrowError(); }); () => { expect(() => pipe.transform(new Object())).toThrowError(); });
}); });
}); });
@ -61,12 +61,12 @@ export function main() {
describe("transform", () => { describe("transform", () => {
it('should return correct value for numbers', () => { it('should return correct value for numbers', () => {
expect(pipe.transform(123, [])).toEqual('USD123'); expect(pipe.transform(123)).toEqual('USD123');
expect(pipe.transform(12, ['EUR', false, '.2'])).toEqual('EUR12.00'); expect(pipe.transform(12, 'EUR', false, '.2')).toEqual('EUR12.00');
}); });
it("should not support other objects", it("should not support other objects",
() => { expect(() => pipe.transform(new Object(), [])).toThrowError(); }); () => { expect(() => pipe.transform(new Object())).toThrowError(); });
}); });
}); });
} }

View File

@ -31,34 +31,34 @@ export function main() {
describe("transform", () => { describe("transform", () => {
it("should not support input other than strings and numbers", () => { it("should not support input other than strings and numbers", () => {
expect(() => pipe.transform({}, ["Douglas", "Hugh"])).toThrow(); expect(() => pipe.transform({}, "Douglas", "Hugh")).toThrow();
expect(() => pipe.transform([1, 2, 3], ["Douglas", "Hugh"])).toThrow(); expect(() => pipe.transform([1, 2, 3], "Douglas", "Hugh")).toThrow();
}); });
it("should not support patterns other than strings and regular expressions", () => { it("should not support patterns other than strings and regular expressions", () => {
expect(() => pipe.transform(str, [{}, "Hugh"])).toThrow(); expect(() => pipe.transform(str, {}, "Hugh")).toThrow();
expect(() => pipe.transform(str, [null, "Hugh"])).toThrow(); expect(() => pipe.transform(str, null, "Hugh")).toThrow();
expect(() => pipe.transform(str, [123, "Hugh"])).toThrow(); expect(() => pipe.transform(str, 123, "Hugh")).toThrow();
}); });
it("should not support replacements other than strings and functions", () => { it("should not support replacements other than strings and functions", () => {
expect(() => pipe.transform(str, ["Douglas", {}])).toThrow(); expect(() => pipe.transform(str, "Douglas", {})).toThrow();
expect(() => pipe.transform(str, ["Douglas", null])).toThrow(); expect(() => pipe.transform(str, "Douglas", null)).toThrow();
expect(() => pipe.transform(str, ["Douglas", 123])).toThrow(); expect(() => pipe.transform(str, "Douglas", 123)).toThrow();
}); });
it("should return a new string with the pattern replaced", () => { it("should return a new string with the pattern replaced", () => {
var result1 = pipe.transform(str, ["Douglas", "Hugh"]); var result1 = pipe.transform(str, "Douglas", "Hugh");
var result2 = pipe.transform(str, [RegExpWrapper.create("a"), "_"]); var result2 = pipe.transform(str, RegExpWrapper.create("a"), "_");
var result3 = pipe.transform(str, [RegExpWrapper.create("a", "i"), "_"]); var result3 = pipe.transform(str, RegExpWrapper.create("a", "i"), "_");
var f = (x => { return "Adams!"; }); var f = (x => { return "Adams!"; });
var result4 = pipe.transform(str, ["Adams", f]); var result4 = pipe.transform(str, "Adams", f);
var result5 = pipe.transform(someNumber, ["2", "4"]); var result5 = pipe.transform(someNumber, "2", "4");
expect(result1).toEqual("Hugh Adams"); expect(result1).toEqual("Hugh Adams");
expect(result2).toEqual("Dougl_s Ad_ms"); expect(result2).toEqual("Dougl_s Ad_ms");

View File

@ -42,47 +42,47 @@ export function main() {
it('should return all items after START index when START is positive and END is omitted', it('should return all items after START index when START is positive and END is omitted',
() => { () => {
expect(pipe.transform(list, [3])).toEqual([4, 5]); expect(pipe.transform(list, 3)).toEqual([4, 5]);
expect(pipe.transform(str, [3])).toEqual('wxyz'); expect(pipe.transform(str, 3)).toEqual('wxyz');
}); });
it('should return last START items when START is negative and END is omitted', () => { it('should return last START items when START is negative and END is omitted', () => {
expect(pipe.transform(list, [-3])).toEqual([3, 4, 5]); expect(pipe.transform(list, -3)).toEqual([3, 4, 5]);
expect(pipe.transform(str, [-3])).toEqual('xyz'); expect(pipe.transform(str, -3)).toEqual('xyz');
}); });
it('should return all items between START and END index when START and END are positive', it('should return all items between START and END index when START and END are positive',
() => { () => {
expect(pipe.transform(list, [1, 3])).toEqual([2, 3]); expect(pipe.transform(list, 1, 3)).toEqual([2, 3]);
expect(pipe.transform(str, [1, 3])).toEqual('uv'); expect(pipe.transform(str, 1, 3)).toEqual('uv');
}); });
it('should return all items between START and END from the end when START and END are negative', it('should return all items between START and END from the end when START and END are negative',
() => { () => {
expect(pipe.transform(list, [-4, -2])).toEqual([2, 3]); expect(pipe.transform(list, -4, -2)).toEqual([2, 3]);
expect(pipe.transform(str, [-4, -2])).toEqual('wx'); expect(pipe.transform(str, -4, -2)).toEqual('wx');
}); });
it('should return an empty value if START is greater than END', () => { it('should return an empty value if START is greater than END', () => {
expect(pipe.transform(list, [4, 2])).toEqual([]); expect(pipe.transform(list, 4, 2)).toEqual([]);
expect(pipe.transform(str, [4, 2])).toEqual(''); expect(pipe.transform(str, 4, 2)).toEqual('');
}); });
it('should return an empty value if START greater than input length', () => { it('should return an empty value if START greater than input length', () => {
expect(pipe.transform(list, [99])).toEqual([]); expect(pipe.transform(list, 99)).toEqual([]);
expect(pipe.transform(str, [99])).toEqual(''); expect(pipe.transform(str, 99)).toEqual('');
}); });
// Makes Edge to disconnect when running the full unit test campaign // Makes Edge to disconnect when running the full unit test campaign
// TODO: remove when issue is solved: https://github.com/angular/angular/issues/4756 // TODO: remove when issue is solved: https://github.com/angular/angular/issues/4756
if (!browserDetection.isEdge) { if (!browserDetection.isEdge) {
it('should return entire input if START is negative and greater than input length', () => { it('should return entire input if START is negative and greater than input length', () => {
expect(pipe.transform(list, [-99])).toEqual([1, 2, 3, 4, 5]); expect(pipe.transform(list, -99)).toEqual([1, 2, 3, 4, 5]);
expect(pipe.transform(str, [-99])).toEqual('tuvwxyz'); expect(pipe.transform(str, -99)).toEqual('tuvwxyz');
}); });
it('should not modify the input list', () => { it('should not modify the input list', () => {
expect(pipe.transform(list, [2])).toEqual([3, 4, 5]); expect(pipe.transform(list, 2)).toEqual([3, 4, 5]);
expect(list).toEqual([1, 2, 3, 4, 5]); expect(list).toEqual([1, 2, 3, 4, 5]);
}); });
} }

View File

@ -103,6 +103,11 @@ export function main() {
.callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')]) .callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')])
.toStmt())) .toStmt()))
.toEqual('observable.listen(listener);'); .toEqual('observable.listen(listener);');
expect(
emitStmt(
o.variable('fn').callMethod(o.BuiltinMethod.bind, [o.variable('someObj')]).toStmt()))
.toEqual('fn;');
}); });
it('should support literals', () => { it('should support literals', () => {

View File

@ -95,6 +95,11 @@ export function main() {
.callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')]) .callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')])
.toStmt())) .toStmt()))
.toEqual('observable.subscribe(listener);'); .toEqual('observable.subscribe(listener);');
expect(
emitStmt(
o.variable('fn').callMethod(o.BuiltinMethod.bind, [o.variable('someObj')]).toStmt()))
.toEqual('fn.bind(someObj);');
}); });
it('should support literals', () => { it('should support literals', () => {

View File

@ -88,8 +88,12 @@ export function main() {
expect(expressions['concatedArray']).toEqual([0, 1]); expect(expressions['concatedArray']).toEqual([0, 1]);
expect(expressions['invokeMethodExternalInstance']) expect(expressions['invokeMethodExternalInstance'])
.toEqual({'data': 'someValue', 'param': 'someParam'}); .toEqual({'data': 'someValue', 'param': 'someParam'});
expect(expressions['invokeMethodExternalInstanceViaBind'])
.toEqual({'data': 'someValue', 'param': 'someParam'});
expect(expressions['invokeMethodDynamicInstance']) expect(expressions['invokeMethodDynamicInstance'])
.toEqual({'data': 'someValue', 'dynamicProp': 'dynamicValue', 'param': 'someParam'}); .toEqual({'data': 'someValue', 'dynamicProp': 'dynamicValue', 'param': 'someParam'});
expect(expressions['invokeMethodDynamicInstanceViaBind'])
.toEqual({'data': 'someValue', 'dynamicProp': 'dynamicValue', 'param': 'someParam'});
}); });
it('should support conditionals', () => { it('should support conditionals', () => {

View File

@ -116,10 +116,24 @@ var _getExpressionsStmts: o.Statement[] = [
'invokeMethodExternalInstance', 'invokeMethodExternalInstance',
o.variable('externalInstance').callMethod('someMethod', [o.literal('someParam')]) o.variable('externalInstance').callMethod('someMethod', [o.literal('someParam')])
], ],
[
'invokeMethodExternalInstanceViaBind',
o.variable('externalInstance')
.prop('someMethod')
.callMethod(o.BuiltinMethod.bind, [o.variable('externalInstance')])
.callFn([o.literal('someParam')])
],
[ [
'invokeMethodDynamicInstance', 'invokeMethodDynamicInstance',
o.variable('dynamicInstance').callMethod('dynamicMethod', [o.literal('someParam')]) o.variable('dynamicInstance').callMethod('dynamicMethod', [o.literal('someParam')])
], ],
[
'invokeMethodDynamicInstanceViaBind',
o.variable('dynamicInstance')
.prop('dynamicMethod')
.callMethod(o.BuiltinMethod.bind, [o.variable('dynamicInstance')])
.callFn([o.literal('someParam')])
],
[ [
'concatedArray', 'concatedArray',
o.literalArr([o.literal(0)]) o.literalArr([o.literal(0)])

View File

@ -96,6 +96,11 @@ export function main() {
.callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')]) .callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')])
.toStmt())) .toStmt()))
.toEqual('observable.subscribe(listener);'); .toEqual('observable.subscribe(listener);');
expect(
emitStmt(
o.variable('fn').callMethod(o.BuiltinMethod.bind, [o.variable('someObj')]).toStmt()))
.toEqual('fn.bind(someObj);');
}); });
it('should support literals', () => { it('should support literals', () => {

View File

@ -460,6 +460,13 @@ export function main() {
})); }));
it('should associate pipes right-to-left', fakeAsync(() => { it('should associate pipes right-to-left', fakeAsync(() => {
var ctx = _bindSimpleValue('name | multiArgPipe:"a":"b" | multiArgPipe:0:1', Person);
ctx.componentInstance.name = 'value';
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['value a b default 0 1 default']);
}));
it('should support calling pure pipes with different number of arguments', fakeAsync(() => {
var ctx = _bindSimpleValue('name | multiArgPipe:"a":"b" | multiArgPipe:0:1:2', Person); var ctx = _bindSimpleValue('name | multiArgPipe:"a":"b" | multiArgPipe:0:1:2', Person);
ctx.componentInstance.name = 'value'; ctx.componentInstance.name = 'value';
ctx.detectChanges(false); ctx.detectChanges(false);
@ -491,6 +498,56 @@ export function main() {
expect(renderLog.log).toEqual(['someProp=Megatron']); expect(renderLog.log).toEqual(['someProp=Megatron']);
})); }));
it('should call pure pipes only if the arguments change', fakeAsync(() => {
var ctx = _bindSimpleValue('name | countingPipe', Person);
// change from undefined -> null
ctx.componentInstance.name = null;
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['null state:0']);
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['null state:0']);
// change from null -> some value
ctx.componentInstance.name = 'bob';
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['null state:0', 'bob state:1']);
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['null state:0', 'bob state:1']);
// change from some value -> some other value
ctx.componentInstance.name = 'bart';
ctx.detectChanges(false);
expect(renderLog.loggedValues)
.toEqual(['null state:0', 'bob state:1', 'bart state:2']);
ctx.detectChanges(false);
expect(renderLog.loggedValues)
.toEqual(['null state:0', 'bob state:1', 'bart state:2']);
}));
it('should call pure pipes that are used multiple times only when the arguments change',
fakeAsync(() => {
var ctx = createCompFixture(`<div [someProp]="name | countingPipe"></div><div [someProp]="age | countingPipe"></div>`, Person);
ctx.componentInstance.name = 'a';
ctx.componentInstance.age = 10;
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1']);
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1']);
ctx.componentInstance.age = 11;
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['a state:0', '10 state:1', '11 state:2']);
}));
it('should call impure pipes on each change detection run', fakeAsync(() => {
var ctx = _bindSimpleValue('name | countingImpurePipe', Person);
ctx.componentInstance.name = 'bob';
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['bob state:0']);
ctx.detectChanges(false);
expect(renderLog.loggedValues).toEqual(['bob state:0', 'bob state:1']);
}));
}); });
describe('event expressions', () => { describe('event expressions', () => {
@ -1014,6 +1071,7 @@ const ALL_DIRECTIVES = CONST_EXPR([
const ALL_PIPES = CONST_EXPR([ const ALL_PIPES = CONST_EXPR([
forwardRef(() => CountingPipe), forwardRef(() => CountingPipe),
forwardRef(() => CountingImpurePipe),
forwardRef(() => MultiArgPipe), forwardRef(() => MultiArgPipe),
forwardRef(() => PipeWithOnDestroy), forwardRef(() => PipeWithOnDestroy),
forwardRef(() => IdentityPipe), forwardRef(() => IdentityPipe),
@ -1086,7 +1144,13 @@ class DirectiveLog {
@Pipe({name: 'countingPipe'}) @Pipe({name: 'countingPipe'})
class CountingPipe implements PipeTransform { class CountingPipe implements PipeTransform {
state: number = 0; state: number = 0;
transform(value, args = null) { return `${value} state:${this.state ++}`; } transform(value) { return `${value} state:${this.state ++}`; }
}
@Pipe({name: 'countingImpurePipe', pure: false})
class CountingImpurePipe implements PipeTransform {
state: number = 0;
transform(value) { return `${value} state:${this.state ++}`; }
} }
@Pipe({name: 'pipeWithOnDestroy'}) @Pipe({name: 'pipeWithOnDestroy'})
@ -1095,27 +1159,22 @@ class PipeWithOnDestroy implements PipeTransform, OnDestroy {
ngOnDestroy() { this.directiveLog.add('pipeWithOnDestroy', 'ngOnDestroy'); } ngOnDestroy() { this.directiveLog.add('pipeWithOnDestroy', 'ngOnDestroy'); }
transform(value, args = null) { return null; } transform(value) { return null; }
} }
@Pipe({name: 'identityPipe'}) @Pipe({name: 'identityPipe'})
class IdentityPipe implements PipeTransform { class IdentityPipe implements PipeTransform {
transform(value, args = null) { return value; } transform(value) { return value; }
} }
@Pipe({name: 'wrappedPipe'}) @Pipe({name: 'wrappedPipe'})
class WrappedPipe implements PipeTransform { class WrappedPipe implements PipeTransform {
transform(value, args = null) { return WrappedValue.wrap(value); } transform(value) { return WrappedValue.wrap(value); }
} }
@Pipe({name: 'multiArgPipe'}) @Pipe({name: 'multiArgPipe'})
class MultiArgPipe implements PipeTransform { class MultiArgPipe implements PipeTransform {
transform(value, args = null) { transform(value, arg1, arg2, arg3 = 'default') { return `${value} ${arg1} ${arg2} ${arg3}`; }
var arg1 = args[0];
var arg2 = args[1];
var arg3 = args.length > 2 ? args[2] : 'default';
return `${value} ${arg1} ${arg2} ${arg3}`;
}
} }
@Component({selector: 'test-cmp', template: '', directives: ALL_DIRECTIVES, pipes: ALL_PIPES}) @Component({selector: 'test-cmp', template: '', directives: ALL_DIRECTIVES, pipes: ALL_PIPES})

View File

@ -2136,7 +2136,7 @@ class SomeViewport {
@Pipe({name: 'double'}) @Pipe({name: 'double'})
class DoublePipe implements PipeTransform, OnDestroy { class DoublePipe implements PipeTransform, OnDestroy {
ngOnDestroy() {} ngOnDestroy() {}
transform(value, args = null) { return `${value}${value}`; } transform(value) { return `${value}${value}`; }
} }
@Directive({selector: '[emitter]', outputs: ['event']}) @Directive({selector: '[emitter]', outputs: ['event']})

View File

@ -152,10 +152,10 @@ class MyComp {
@Pipe({name: 'somePipe', pure: true}) @Pipe({name: 'somePipe', pure: true})
class PlatformPipe implements PipeTransform { class PlatformPipe implements PipeTransform {
transform(value: any, args: any[]): any { return 'somePlatformPipe'; } transform(value: any): any { return 'somePlatformPipe'; }
} }
@Pipe({name: 'somePipe', pure: true}) @Pipe({name: 'somePipe', pure: true})
class CustomPipe implements PipeTransform { class CustomPipe implements PipeTransform {
transform(value: any, args: any[]): any { return 'someCustomPipe'; } transform(value: any): any { return 'someCustomPipe'; }
} }

View File

@ -232,38 +232,38 @@ class PushComponentNeedsChangeDetectorRef {
} }
@Pipe({name: 'purePipe', pure: true}) @Pipe({name: 'purePipe', pure: true})
class PurePipe { class PurePipe implements PipeTransform {
constructor() {} constructor() {}
transform(value: any, args: any[] = null): any { return this; } transform(value: any): any { return this; }
} }
@Pipe({name: 'impurePipe', pure: false}) @Pipe({name: 'impurePipe', pure: false})
class ImpurePipe { class ImpurePipe implements PipeTransform {
constructor() {} constructor() {}
transform(value: any, args: any[] = null): any { return this; } transform(value: any): any { return this; }
} }
@Pipe({name: 'pipeNeedsChangeDetectorRef'}) @Pipe({name: 'pipeNeedsChangeDetectorRef'})
class PipeNeedsChangeDetectorRef { class PipeNeedsChangeDetectorRef {
constructor(public changeDetectorRef: ChangeDetectorRef) {} constructor(public changeDetectorRef: ChangeDetectorRef) {}
transform(value: any, args: any[] = null): any { return this; } transform(value: any): any { return this; }
} }
@Pipe({name: 'pipeNeedsService'}) @Pipe({name: 'pipeNeedsService'})
export class PipeNeedsService implements PipeTransform { export class PipeNeedsService implements PipeTransform {
service: any; service: any;
constructor(@Inject("service") service) { this.service = service; } constructor(@Inject("service") service) { this.service = service; }
transform(value: any, args: any[] = null): any { return this; } transform(value: any): any { return this; }
} }
@Pipe({name: 'duplicatePipe'}) @Pipe({name: 'duplicatePipe'})
export class DuplicatePipe1 implements PipeTransform { export class DuplicatePipe1 implements PipeTransform {
transform(value: any, args: any[] = null): any { return this; } transform(value: any): any { return this; }
} }
@Pipe({name: 'duplicatePipe'}) @Pipe({name: 'duplicatePipe'})
export class DuplicatePipe2 implements PipeTransform { export class DuplicatePipe2 implements PipeTransform {
transform(value: any, args: any[] = null): any { return this; } transform(value: any): any { return this; }
} }
@Component({selector: 'root'}) @Component({selector: 'root'})
@ -654,7 +654,7 @@ export function main() {
it('should cache pure pipes', fakeAsync(() => { it('should cache pure pipes', fakeAsync(() => {
var el = createComp( var el = createComp(
'<div [simpleDirective]="true | purePipe"></div><div [simpleDirective]="true | purePipe"></div>', '<div [simpleDirective]="true | purePipe"></div><div *ngIf="true" [simpleDirective]="true | purePipe"></div>',
tcb); tcb);
var purePipe1 = el.children[0].inject(SimpleDirective).value; var purePipe1 = el.children[0].inject(SimpleDirective).value;
var purePipe2 = el.children[1].inject(SimpleDirective).value; var purePipe2 = el.children[1].inject(SimpleDirective).value;

View File

@ -575,7 +575,7 @@ const COMMON = [
'AsyncPipe', 'AsyncPipe',
'AsyncPipe.constructor(_ref:ChangeDetectorRef)', 'AsyncPipe.constructor(_ref:ChangeDetectorRef)',
'AsyncPipe.ngOnDestroy():void', 'AsyncPipe.ngOnDestroy():void',
'AsyncPipe.transform(obj:Observable<any>|Promise<any>|EventEmitter<any>, args:any[]):any', 'AsyncPipe.transform(obj:Observable<any>|Promise<any>|EventEmitter<any>):any',
'CheckboxControlValueAccessor', 'CheckboxControlValueAccessor',
'CheckboxControlValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)', 'CheckboxControlValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)',
'CheckboxControlValueAccessor.onChange:any', 'CheckboxControlValueAccessor.onChange:any',
@ -610,12 +610,12 @@ const COMMON = [
'ControlValueAccessor.registerOnTouched(fn:any):void', 'ControlValueAccessor.registerOnTouched(fn:any):void',
'ControlValueAccessor.writeValue(obj:any):void', 'ControlValueAccessor.writeValue(obj:any):void',
'CurrencyPipe', 'CurrencyPipe',
'CurrencyPipe.transform(value:any, args:any[]):string', 'CurrencyPipe.transform(value:any, currencyCode:string, symbolDisplay:boolean, digits:string):string',
'DatePipe', 'DatePipe',
'DatePipe.supports(obj:any):boolean', 'DatePipe.supports(obj:any):boolean',
'DatePipe.transform(value:any, args:any[]):string', 'DatePipe.transform(value:any, pattern:string):string',
'DecimalPipe', 'DecimalPipe',
'DecimalPipe.transform(value:any, args:any[]):string', 'DecimalPipe.transform(value:any, digits:string):string',
'DefaultValueAccessor', 'DefaultValueAccessor',
'DefaultValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)', 'DefaultValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)',
'DefaultValueAccessor.onChange:any', 'DefaultValueAccessor.onChange:any',
@ -636,13 +636,13 @@ const COMMON = [
'FormBuilder.control(value:Object, validator:ValidatorFn, asyncValidator:AsyncValidatorFn):Control', 'FormBuilder.control(value:Object, validator:ValidatorFn, asyncValidator:AsyncValidatorFn):Control',
'FormBuilder.group(controlsConfig:{[key:string]:any}, extra:{[key:string]:any}):ControlGroup', 'FormBuilder.group(controlsConfig:{[key:string]:any}, extra:{[key:string]:any}):ControlGroup',
'I18nPluralPipe', 'I18nPluralPipe',
'I18nPluralPipe.transform(value:number, args:any[]):string', 'I18nPluralPipe.transform(value:number, pluralMap:{[count:string]:string}):string',
'I18nSelectPipe', 'I18nSelectPipe',
'I18nSelectPipe.transform(value:string, args:any[]):string', 'I18nSelectPipe.transform(value:string, mapping:{[key:string]:string}):string',
'JsonPipe', 'JsonPipe',
'JsonPipe.transform(value:any, args:any[]):string', 'JsonPipe.transform(value:any):string',
'LowerCasePipe', 'LowerCasePipe',
'LowerCasePipe.transform(value:string, args:any[]):string', 'LowerCasePipe.transform(value:string):string',
'MaxLengthValidator', 'MaxLengthValidator',
'MaxLengthValidator.constructor(maxLength:string)', 'MaxLengthValidator.constructor(maxLength:string)',
'MaxLengthValidator.validate(c:AbstractControl):{[key:string]:any}', 'MaxLengthValidator.validate(c:AbstractControl):{[key:string]:any}',
@ -790,9 +790,9 @@ const COMMON = [
'PatternValidator.constructor(pattern:string)', 'PatternValidator.constructor(pattern:string)',
'PatternValidator.validate(c:AbstractControl):{[key:string]:any}', 'PatternValidator.validate(c:AbstractControl):{[key:string]:any}',
'PercentPipe', 'PercentPipe',
'PercentPipe.transform(value:any, args:any[]):string', 'PercentPipe.transform(value:any, digits:string):string',
'ReplacePipe', 'ReplacePipe',
'ReplacePipe.transform(value:any, args:any[]):any', 'ReplacePipe.transform(value:any, pattern:string|RegExp, replacement:Function|string):any',
'RequiredValidator', 'RequiredValidator',
'SelectControlValueAccessor', 'SelectControlValueAccessor',
'SelectControlValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)', 'SelectControlValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)',
@ -803,9 +803,9 @@ const COMMON = [
'SelectControlValueAccessor.value:any', 'SelectControlValueAccessor.value:any',
'SelectControlValueAccessor.writeValue(value:any):void', 'SelectControlValueAccessor.writeValue(value:any):void',
'SlicePipe', 'SlicePipe',
'SlicePipe.transform(value:any, args:any[]):any', 'SlicePipe.transform(value:any, start:number, end:number):any',
'UpperCasePipe', 'UpperCasePipe',
'UpperCasePipe.transform(value:string, args:any[]):string', 'UpperCasePipe.transform(value:string):string',
'Validator', 'Validator',
'Validator.validate(c:AbstractControl):{[key:string]:any}', 'Validator.validate(c:AbstractControl):{[key:string]:any}',
'Validators', 'Validators',