diff --git a/modules/angular2/src/common/pipes/async_pipe.ts b/modules/angular2/src/common/pipes/async_pipe.ts index 9131cfb147..b91130c693 100644 --- a/modules/angular2/src/common/pipes/async_pipe.ts +++ b/modules/angular2/src/common/pipes/async_pipe.ts @@ -1,13 +1,6 @@ import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang'; import {ObservableWrapper, Observable, EventEmitter} from 'angular2/src/facade/async'; -import { - Pipe, - Injectable, - ChangeDetectorRef, - OnDestroy, - PipeTransform, - WrappedValue -} from 'angular2/core'; +import {Pipe, Injectable, ChangeDetectorRef, OnDestroy, WrappedValue} from 'angular2/core'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @@ -55,7 +48,7 @@ var __unused: Promise; // avoid unused import when Promise union types are */ @Pipe({name: 'async', pure: false}) @Injectable() -export class AsyncPipe implements PipeTransform, OnDestroy { +export class AsyncPipe implements OnDestroy { /** @internal */ _latestValue: Object = null; /** @internal */ @@ -76,7 +69,7 @@ export class AsyncPipe implements PipeTransform, OnDestroy { } } - transform(obj: Observable| Promise| EventEmitter, args?: any[]): any { + transform(obj: Observable| Promise| EventEmitter): any { if (isBlank(this._obj)) { if (isPresent(obj)) { this._subscribe(obj); diff --git a/modules/angular2/src/common/pipes/date_pipe.ts b/modules/angular2/src/common/pipes/date_pipe.ts index 27f38308a6..9ebab2e547 100644 --- a/modules/angular2/src/common/pipes/date_pipe.ts +++ b/modules/angular2/src/common/pipes/date_pipe.ts @@ -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 (!this.supports(value)) { throw new InvalidPipeArgumentException(DatePipe, value); } - var pattern: string = isPresent(args) && args.length > 0 ? args[0] : 'mediumDate'; if (isNumber(value)) { value = DateWrapper.fromMillis(value); } diff --git a/modules/angular2/src/common/pipes/i18n_plural_pipe.ts b/modules/angular2/src/common/pipes/i18n_plural_pipe.ts index 0501441e3a..0959931b48 100644 --- a/modules/angular2/src/common/pipes/i18n_plural_pipe.ts +++ b/modules/angular2/src/common/pipes/i18n_plural_pipe.ts @@ -45,10 +45,9 @@ var interpolationExp: RegExp = RegExpWrapper.create('#'); @Pipe({name: 'i18nPlural', pure: true}) @Injectable() export class I18nPluralPipe implements PipeTransform { - transform(value: number, args: any[] = null): string { + transform(value: number, pluralMap: {[count: string]: string}): string { var key: string; var valueStr: string; - var pluralMap: {[count: string]: string} = <{[count: string]: string}>(args[0]); if (!isStringMap(pluralMap)) { throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap); diff --git a/modules/angular2/src/common/pipes/i18n_select_pipe.ts b/modules/angular2/src/common/pipes/i18n_select_pipe.ts index 04722d657f..b5a627303d 100644 --- a/modules/angular2/src/common/pipes/i18n_select_pipe.ts +++ b/modules/angular2/src/common/pipes/i18n_select_pipe.ts @@ -36,8 +36,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @Pipe({name: 'i18nSelect', pure: true}) @Injectable() export class I18nSelectPipe implements PipeTransform { - transform(value: string, args: any[] = null): string { - var mapping: {[key: string]: string} = <{[count: string]: string}>(args[0]); + transform(value: string, mapping: {[key: string]: string}): string { if (!isStringMap(mapping)) { throw new InvalidPipeArgumentException(I18nSelectPipe, mapping); } diff --git a/modules/angular2/src/common/pipes/json_pipe.ts b/modules/angular2/src/common/pipes/json_pipe.ts index 99bcf97b7f..63b251915f 100644 --- a/modules/angular2/src/common/pipes/json_pipe.ts +++ b/modules/angular2/src/common/pipes/json_pipe.ts @@ -11,5 +11,5 @@ import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core'; @Pipe({name: 'json', pure: false}) @Injectable() export class JsonPipe implements PipeTransform { - transform(value: any, args: any[] = null): string { return Json.stringify(value); } + transform(value: any): string { return Json.stringify(value); } } diff --git a/modules/angular2/src/common/pipes/lowercase_pipe.ts b/modules/angular2/src/common/pipes/lowercase_pipe.ts index 3fe1069054..ddc27a9b9a 100644 --- a/modules/angular2/src/common/pipes/lowercase_pipe.ts +++ b/modules/angular2/src/common/pipes/lowercase_pipe.ts @@ -13,7 +13,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @Pipe({name: 'lowercase'}) @Injectable() export class LowerCasePipe implements PipeTransform { - transform(value: string, args: any[] = null): string { + transform(value: string): string { if (isBlank(value)) return value; if (!isString(value)) { throw new InvalidPipeArgumentException(LowerCasePipe, value); diff --git a/modules/angular2/src/common/pipes/number_pipe.ts b/modules/angular2/src/common/pipes/number_pipe.ts index d26bd53128..2aaef1b3bc 100644 --- a/modules/angular2/src/common/pipes/number_pipe.ts +++ b/modules/angular2/src/common/pipes/number_pipe.ts @@ -11,7 +11,6 @@ import { import {BaseException, WrappedException} from 'angular2/src/facade/exceptions'; import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl'; import {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core'; -import {ListWrapper} from 'angular2/src/facade/collection'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @@ -87,8 +86,7 @@ export class NumberPipe { @Pipe({name: 'number'}) @Injectable() export class DecimalPipe extends NumberPipe implements PipeTransform { - transform(value: any, args: any[]): string { - var digits: string = ListWrapper.first(args); + transform(value: any, digits: string = null): string { return NumberPipe._format(value, NumberFormatStyle.Decimal, digits); } } @@ -113,8 +111,7 @@ export class DecimalPipe extends NumberPipe implements PipeTransform { @Pipe({name: 'percent'}) @Injectable() export class PercentPipe extends NumberPipe implements PipeTransform { - transform(value: any, args: any[]): string { - var digits: string = ListWrapper.first(args); + transform(value: any, digits: string = null): string { return NumberPipe._format(value, NumberFormatStyle.Percent, digits); } } @@ -143,10 +140,8 @@ export class PercentPipe extends NumberPipe implements PipeTransform { @Pipe({name: 'currency'}) @Injectable() export class CurrencyPipe extends NumberPipe implements PipeTransform { - transform(value: any, args: any[]): string { - var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD'; - var symbolDisplay: boolean = isPresent(args) && args.length > 1 ? args[1] : false; - var digits: string = isPresent(args) && args.length > 2 ? args[2] : null; + transform(value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false, + digits: string = null): string { return NumberPipe._format(value, NumberFormatStyle.Currency, digits, currencyCode, symbolDisplay); } diff --git a/modules/angular2/src/common/pipes/replace_pipe.ts b/modules/angular2/src/common/pipes/replace_pipe.ts index de3c0e9f73..bfc643b17f 100644 --- a/modules/angular2/src/common/pipes/replace_pipe.ts +++ b/modules/angular2/src/common/pipes/replace_pipe.ts @@ -6,7 +6,6 @@ import { RegExpWrapper, StringWrapper } from 'angular2/src/facade/lang'; -import {BaseException} from 'angular2/src/facade/exceptions'; import {Injectable, PipeTransform, Pipe} from 'angular2/core'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @@ -39,11 +38,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @Pipe({name: 'replace'}) @Injectable() export class ReplacePipe implements PipeTransform { - transform(value: any, args: any[]): any { - if (isBlank(args) || args.length !== 2) { - throw new BaseException('ReplacePipe requires two arguments'); - } - + transform(value: any, pattern: string | RegExp, replacement: Function | string): any { if (isBlank(value)) { return value; } @@ -53,9 +48,6 @@ export class ReplacePipe implements PipeTransform { } var input = value.toString(); - var pattern = args[0]; - var replacement = args[1]; - if (!this._supportedPattern(pattern)) { throw new InvalidPipeArgumentException(ReplacePipe, pattern); @@ -67,16 +59,16 @@ export class ReplacePipe implements PipeTransform { // var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern); if (isFunction(replacement)) { - var rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern; + var rgxPattern = isString(pattern) ? RegExpWrapper.create(pattern) : pattern; - return StringWrapper.replaceAllMapped(input, rgxPattern, replacement); + return StringWrapper.replaceAllMapped(input, rgxPattern, replacement); } if (pattern instanceof RegExp) { // use the replaceAll variant - return StringWrapper.replaceAll(input, pattern, replacement); + return StringWrapper.replaceAll(input, pattern, replacement); } - return StringWrapper.replace(input, pattern, replacement); + return StringWrapper.replace(input, pattern, replacement); } private _supportedInput(input: any): boolean { return isString(input) || isNumber(input); } diff --git a/modules/angular2/src/common/pipes/slice_pipe.ts b/modules/angular2/src/common/pipes/slice_pipe.ts index 37d490da52..2e55b63b74 100644 --- a/modules/angular2/src/common/pipes/slice_pipe.ts +++ b/modules/angular2/src/common/pipes/slice_pipe.ts @@ -1,5 +1,4 @@ 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 {Injectable, PipeTransform, WrappedValue, Pipe} from 'angular2/core'; import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @@ -59,16 +58,11 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @Pipe({name: 'slice', pure: false}) @Injectable() export class SlicePipe implements PipeTransform { - transform(value: any, args: any[] = null): any { - if (isBlank(args) || args.length == 0) { - throw new BaseException('Slice pipe requires one argument'); - } + transform(value: any, start: number, end: number = null): any { if (!this.supports(value)) { throw new InvalidPipeArgumentException(SlicePipe, value); } if (isBlank(value)) return value; - var start: number = args[0]; - var end: number = args.length > 1 ? args[1] : null; if (isString(value)) { return StringWrapper.slice(value, start, end); } diff --git a/modules/angular2/src/common/pipes/uppercase_pipe.ts b/modules/angular2/src/common/pipes/uppercase_pipe.ts index f91d2ce460..37cb97dcd5 100644 --- a/modules/angular2/src/common/pipes/uppercase_pipe.ts +++ b/modules/angular2/src/common/pipes/uppercase_pipe.ts @@ -13,7 +13,7 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception'; @Pipe({name: 'uppercase'}) @Injectable() export class UpperCasePipe implements PipeTransform { - transform(value: string, args: any[] = null): string { + transform(value: string): string { if (isBlank(value)) return value; if (!isString(value)) { throw new InvalidPipeArgumentException(UpperCasePipe, value); diff --git a/modules/angular2/src/compiler/identifiers.ts b/modules/angular2/src/compiler/identifiers.ts index 453ed25ce4..c07d96b5af 100644 --- a/modules/angular2/src/compiler/identifiers.ts +++ b/modules/angular2/src/compiler/identifiers.ts @@ -5,7 +5,18 @@ import { ViewUtils, flattenNestedViewRenderNodes, interpolate, - checkBinding + checkBinding, + castByValue, + pureProxy1, + pureProxy2, + pureProxy3, + pureProxy4, + pureProxy5, + pureProxy6, + pureProxy7, + pureProxy8, + pureProxy9, + pureProxy10 } from 'angular2/src/core/linker/view_utils'; import { uninitialized, @@ -59,6 +70,7 @@ var impFlattenNestedViewRenderNodes = flattenNestedViewRenderNodes; var impDevModeEqual = devModeEqual; var impInterpolate = interpolate; var impCheckBinding = checkBinding; +var impCastByValue = castByValue; export class Identifiers { static ViewUtils = new CompileIdentifierMetadata({ @@ -162,6 +174,31 @@ export class Identifiers { {name: 'devModeEqual', moduleUrl: CD_MODULE_URL, runtime: impDevModeEqual}); static interpolate = new CompileIdentifierMetadata( {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 { diff --git a/modules/angular2/src/compiler/output/abstract_emitter.ts b/modules/angular2/src/compiler/output/abstract_emitter.ts index 5ea387d79a..c84d7809ff 100644 --- a/modules/angular2/src/compiler/output/abstract_emitter.ts +++ b/modules/angular2/src/compiler/output/abstract_emitter.ts @@ -197,6 +197,11 @@ export abstract class AbstractEmitterVisitor implements o.StatementVisitor, o.Ex var name = expr.name; if (isPresent(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}(`); this.visitAllExpressions(expr.args, ctx, `,`); diff --git a/modules/angular2/src/compiler/output/abstract_js_emitter.ts b/modules/angular2/src/compiler/output/abstract_js_emitter.ts index 9b85f356e5..1708c8ab77 100644 --- a/modules/angular2/src/compiler/output/abstract_js_emitter.ts +++ b/modules/angular2/src/compiler/output/abstract_js_emitter.ts @@ -153,6 +153,9 @@ export abstract class AbstractJsEmitterVisitor extends AbstractEmitterVisitor { case o.BuiltinMethod.SubscribeObservable: name = 'subscribe'; break; + case o.BuiltinMethod.bind: + name = 'bind'; + break; default: throw new BaseException(`Unknown builtin method: ${method}`); } diff --git a/modules/angular2/src/compiler/output/dart_emitter.ts b/modules/angular2/src/compiler/output/dart_emitter.ts index 9561a02efb..6abb8918cf 100644 --- a/modules/angular2/src/compiler/output/dart_emitter.ts +++ b/modules/angular2/src/compiler/output/dart_emitter.ts @@ -213,6 +213,9 @@ class _DartEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisito case o.BuiltinMethod.SubscribeObservable: name = 'listen'; break; + case o.BuiltinMethod.bind: + name = null; + break; default: throw new BaseException(`Unknown builtin method: ${method}`); } diff --git a/modules/angular2/src/compiler/output/interpretive_view.ts b/modules/angular2/src/compiler/output/interpretive_view.ts index 26b61766eb..f00368d32b 100644 --- a/modules/angular2/src/compiler/output/interpretive_view.ts +++ b/modules/angular2/src/compiler/output/interpretive_view.ts @@ -17,8 +17,7 @@ export class InterpretiveAppViewInstanceFactory implements InstanceFactory { class _InterpretiveAppView extends AppView implements DynamicInstance { constructor(args: any[], public props: Map, public getters: Map, public methods: Map) { - super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], - args[10]); + super(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); } createInternal(rootSelector: string | any): AppElement { var m = this.methods.get('createInternal'); diff --git a/modules/angular2/src/compiler/output/output_ast.ts b/modules/angular2/src/compiler/output/output_ast.ts index c4e7366d7a..3d41540f42 100644 --- a/modules/angular2/src/compiler/output/output_ast.ts +++ b/modules/angular2/src/compiler/output/output_ast.ts @@ -244,7 +244,8 @@ export class WritePropExpr extends Expression { export enum BuiltinMethod { ConcatArray, - SubscribeObservable + SubscribeObservable, + bind } export class InvokeMethodExpr extends Expression { diff --git a/modules/angular2/src/compiler/output/output_interpreter.ts b/modules/angular2/src/compiler/output/output_interpreter.ts index 3451a3d22c..2b9ec6bc0b 100644 --- a/modules/angular2/src/compiler/output/output_interpreter.ts +++ b/modules/angular2/src/compiler/output/output_interpreter.ts @@ -187,6 +187,13 @@ class StatementInterpreter implements o.StatementVisitor, o.ExpressionVisitor { case o.BuiltinMethod.SubscribeObservable: result = ObservableWrapper.subscribe(receiver, args[0]); break; + case o.BuiltinMethod.bind: + if (IS_DART) { + result = receiver; + } else { + result = receiver.bind(args[0]); + } + break; default: 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); } else if (di.getters.has(ast.name)) { result = di.getters.get(ast.name)(); + } else if (di.methods.has(ast.name)) { + result = di.methods.get(ast.name); } else { result = reflector.getter(ast.name)(receiver); } diff --git a/modules/angular2/src/compiler/output/ts_emitter.ts b/modules/angular2/src/compiler/output/ts_emitter.ts index 2edc5e81a2..960372b540 100644 --- a/modules/angular2/src/compiler/output/ts_emitter.ts +++ b/modules/angular2/src/compiler/output/ts_emitter.ts @@ -287,6 +287,9 @@ class _TsEmitterVisitor extends AbstractEmitterVisitor implements o.TypeVisitor case o.BuiltinMethod.SubscribeObservable: name = 'subscribe'; break; + case o.BuiltinMethod.bind: + name = 'bind'; + break; default: throw new BaseException(`Unknown builtin method: ${method}`); } diff --git a/modules/angular2/src/compiler/view_compiler/compile_element.ts b/modules/angular2/src/compiler/view_compiler/compile_element.ts index 6c5b0446d5..020226f17f 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_element.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_element.ts @@ -324,7 +324,6 @@ export class CompileElement extends CompileNode { private _getDependency(requestingProviderType: ProviderAstType, dep: CompileDiDependencyMetadata): o.Expression { var currElement: CompileElement = this; - var currView = currElement.view; var result = null; if (dep.isValue) { result = o.literal(dep.value); @@ -332,14 +331,9 @@ export class CompileElement extends CompileNode { if (isBlank(result) && !dep.isSkipSelf) { result = this._getLocalDependency(requestingProviderType, dep); } - var resultViewPath = []; // check parent elements while (isBlank(result) && !currElement.parent.isNull()) { currElement = currElement.parent; - while (currElement.view !== currView && currView != null) { - currView = currView.declarationElement.view; - resultViewPath.push(currView); - } result = currElement._getLocalDependency(ProviderAstType.PublicService, new CompileDiDependencyMetadata({token: dep.token})); } @@ -350,7 +344,7 @@ export class CompileElement extends CompileNode { if (isBlank(result)) { result = o.NULL_EXPR; } - return getPropertyInView(result, resultViewPath); + return getPropertyInView(result, this.view, currElement.view); } } diff --git a/modules/angular2/src/compiler/view_compiler/compile_pipe.ts b/modules/angular2/src/compiler/view_compiler/compile_pipe.ts new file mode 100644 index 0000000000..4c62c93133 --- /dev/null +++ b/modules/angular2/src/compiler/view_compiler/compile_pipe.ts @@ -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; +} diff --git a/modules/angular2/src/compiler/view_compiler/compile_query.ts b/modules/angular2/src/compiler/view_compiler/compile_query.ts index 64c8b40c52..63a88e4e82 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_query.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_query.ts @@ -30,14 +30,12 @@ export class CompileQuery { addValue(value: o.Expression, view: CompileView) { var currentView = view; var elPath: CompileElement[] = []; - var viewPath: CompileView[] = []; while (isPresent(currentView) && currentView !== this.view) { var parentEl = currentView.declarationElement; elPath.unshift(parentEl); currentView = parentEl.view; - viewPath.push(currentView); } - var queryListForDirtyExpr = getPropertyInView(this.queryList, viewPath); + var queryListForDirtyExpr = getPropertyInView(this.queryList, view, this.view); var viewValues = this._values; elPath.forEach((el) => { diff --git a/modules/angular2/src/compiler/view_compiler/compile_view.ts b/modules/angular2/src/compiler/view_compiler/compile_view.ts index 41fad43044..c0c414226e 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_view.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_view.ts @@ -1,14 +1,13 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang'; -import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; -import {BaseException} from 'angular2/src/facade/exceptions'; +import {ListWrapper, StringMapWrapper, MapWrapper} from 'angular2/src/facade/collection'; import * as o from '../output/output_ast'; -import {Identifiers, identifierToken} from '../identifiers'; import {EventHandlerVars} from './constants'; import {CompileQuery, createQueryList, addQueryToTokenMap} from './compile_query'; import {NameResolver} from './expression_converter'; import {CompileElement, CompileNode} from './compile_element'; import {CompileMethod} from './compile_method'; +import {CompilePipe} from './compile_pipe'; import {ViewType} from 'angular2/src/core/linker/view_type'; import { CompileDirectiveMetadata, @@ -20,17 +19,12 @@ import { getViewFactoryName, injectFromViewParentInjector, createDiTokenExpression, - getPropertyInView + getPropertyInView, + createPureProxy } from './util'; import {CompilerConfig} from '../config'; import {CompileBinding} from './compile_binding'; -import {bindPipeDestroyLifecycleCallbacks} from './lifecycle_binder'; - -export class CompilePipe { - constructor() {} -} - export class CompileView implements NameResolver { public viewType: ViewType; public viewQueries: CompileTokenMap; @@ -60,7 +54,8 @@ export class CompileView implements NameResolver { public subscriptions: o.Expression[] = []; public componentView: CompileView; - public pipes = new Map(); + public purePipes = new Map(); + public pipes: CompilePipe[] = []; public variables = new Map(); public className: string; public classType: o.Type; @@ -68,6 +63,7 @@ export class CompileView implements NameResolver { public literalArrayCount = 0; public literalMapCount = 0; + public pipeCount = 0; constructor(public component: CompileDirectiveMetadata, public genConfig: CompilerConfig, public pipeMetas: CompilePipeMetadata[], public styles: o.Expression, @@ -124,39 +120,17 @@ export class CompileView implements NameResolver { } } - createPipe(name: string): o.Expression { - var pipeMeta: CompilePipeMetadata = null; - for (var i = this.pipeMetas.length - 1; i >= 0; i--) { - var localPipeMeta = this.pipeMetas[i]; - if (localPipeMeta.name == name) { - pipeMeta = localPipeMeta; - break; + callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { + var compView = this.componentView; + var pipe = compView.purePipes.get(name); + if (isBlank(pipe)) { + pipe = new CompilePipe(compView, name); + if (pipe.pure) { + compView.purePipes.set(name, pipe); } + compView.pipes.push(pipe); } - if (isBlank(pipeMeta)) { - 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; + return pipe.call(this, [input].concat(args)); } getVariable(name: string): o.Expression { @@ -165,29 +139,49 @@ export class CompileView implements NameResolver { } var currView: CompileView = this; var result = currView.variables.get(name); - var viewPath = []; while (isBlank(result) && isPresent(currView.declarationElement.view)) { currView = currView.declarationElement.view; result = currView.variables.get(name); - viewPath.push(currView); } if (isPresent(result)) { - return getPropertyInView(result, viewPath); + return getPropertyInView(result, this, currView); } else { return null; } } createLiteralArray(values: o.Expression[]): o.Expression { - return o.THIS_EXPR.callMethod('literalArray', - [o.literal(this.literalArrayCount++), o.literalArr(values)]); + var proxyExpr = o.THIS_EXPR.prop(`_arr_${this.literalArrayCount++}`); + 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)); + } + createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalArr(proxyReturnEntries))]), + values.length, proxyExpr, this); + return proxyExpr.callFn(values); } - createLiteralMap(values: Array>): o.Expression { - return o.THIS_EXPR.callMethod('literalMap', - [o.literal(this.literalMapCount++), o.literalMap(values)]); + + createLiteralMap(entries: Array>): o.Expression { + var proxyExpr = o.THIS_EXPR.prop(`_map_${this.literalMapCount++}`); + var proxyParams: o.FnParam[] = []; + var proxyReturnEntries: Array> = []; + 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(entries[i][1]); + } + createPureProxy(o.fn(proxyParams, [new o.ReturnStatement(o.literalMap(proxyReturnEntries))]), + entries.length, proxyExpr, this); + return proxyExpr.callFn(values); } afterNodes() { + this.pipes.forEach((pipe) => pipe.create()); this.viewQueries.values().forEach( (queries) => queries.forEach((query) => query.afterChildren(this.updateViewQueriesMethod))); } diff --git a/modules/angular2/src/compiler/view_compiler/expression_converter.ts b/modules/angular2/src/compiler/view_compiler/expression_converter.ts index c50f19595b..b94d077ae5 100644 --- a/modules/angular2/src/compiler/view_compiler/expression_converter.ts +++ b/modules/angular2/src/compiler/view_compiler/expression_converter.ts @@ -8,7 +8,7 @@ import {isBlank, isPresent, isArray, CONST_EXPR} from 'angular2/src/facade/lang' var IMPLICIT_RECEIVER = o.variable('#implicit'); export interface NameResolver { - createPipe(name: string): o.Expression; + callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression; getVariable(name: string): o.Expression; createLiteralArray(values: o.Expression[]): o.Expression; createLiteralMap(values: Array>): o.Expression; @@ -132,13 +132,11 @@ class _AstToIrVisitor implements cdAst.AstVisitor { ast.falseExp.visit(this, _Mode.Expression))); } visitPipe(ast: cdAst.BindingPipe, mode: _Mode): any { - var pipeInstance = this._nameResolver.createPipe(ast.name); var input = ast.exp.visit(this, _Mode.Expression); var args = this.visitAll(ast.args, _Mode.Expression); + var value = this._nameResolver.callPipe(ast.name, input, args); this.needsValueUnwrapper = true; - return convertToStatementIfNeeded( - mode, this._valueUnwrapper.callMethod( - 'unwrap', [pipeInstance.callMethod('transform', [input, o.literalArr(args)])])); + return convertToStatementIfNeeded(mode, this._valueUnwrapper.callMethod('unwrap', [value])); } visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { return convertToStatementIfNeeded(mode, ast.target.visit(this, _Mode.Expression) diff --git a/modules/angular2/src/compiler/view_compiler/lifecycle_binder.ts b/modules/angular2/src/compiler/view_compiler/lifecycle_binder.ts index a70428190e..154b468875 100644 --- a/modules/angular2/src/compiler/view_compiler/lifecycle_binder.ts +++ b/modules/angular2/src/compiler/view_compiler/lifecycle_binder.ts @@ -78,10 +78,10 @@ export function bindDirectiveDestroyLifecycleCallbacks(directiveMeta: CompileDir } } -export function bindPipeDestroyLifecycleCallbacks( - pipeMeta: CompilePipeMetadata, directiveInstance: o.Expression, view: CompileView) { +export function bindPipeDestroyLifecycleCallbacks(pipeMeta: CompilePipeMetadata, + pipeInstance: o.Expression, view: CompileView) { var onDestroyMethod = view.destroyMethod; if (pipeMeta.lifecycleHooks.indexOf(LifecycleHooks.OnDestroy) !== -1) { - onDestroyMethod.addStmt(directiveInstance.callMethod('ngOnDestroy', []).toStmt()); + onDestroyMethod.addStmt(pipeInstance.callMethod('ngOnDestroy', []).toStmt()); } } diff --git a/modules/angular2/src/compiler/view_compiler/util.ts b/modules/angular2/src/compiler/view_compiler/util.ts index fb589bffa1..94d76ba2f9 100644 --- a/modules/angular2/src/compiler/view_compiler/util.ts +++ b/modules/angular2/src/compiler/view_compiler/util.ts @@ -1,4 +1,5 @@ import {isPresent, isBlank} from 'angular2/src/facade/lang'; +import {BaseException} from 'angular2/src/facade/exceptions'; import * as o from '../output/output_ast'; import { @@ -7,22 +8,29 @@ import { CompileIdentifierMetadata } from '../compile_metadata'; import {CompileView} from './compile_view'; +import {Identifiers} from '../identifiers'; -export function getPropertyInView(property: o.Expression, viewPath: CompileView[]): o.Expression { - if (viewPath.length === 0) { +export function getPropertyInView(property: o.Expression, callingView: CompileView, + definedView: CompileView): o.Expression { + if (callingView === definedView) { return property; } else { var viewProp: o.Expression = o.THIS_EXPR; - for (var i = 0; i < viewPath.length; i++) { - viewProp = viewProp.prop('declarationAppElement').prop('parentView'); + var currView: CompileView = callingView; + 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) { - var lastView = viewPath[viewPath.length - 1]; let readPropExpr: o.ReadPropExpr = property; // Note: Don't cast for members of the AppView base class... - if (lastView.fields.some((field) => field.name == readPropExpr.name) || - lastView.getters.some((field) => field.name == readPropExpr.name)) { - viewProp = viewProp.cast(lastView.classType); + if (definedView.fields.some((field) => field.name == readPropExpr.name) || + definedView.getters.some((field) => field.name == readPropExpr.name)) { + viewProp = viewProp.cast(definedView.classType); } } return o.replaceVarInExpression(o.THIS_EXPR.name, viewProp, property); @@ -77,3 +85,15 @@ export function createFlatArray(expressions: o.Expression[]): o.Expression { } 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()); +} diff --git a/modules/angular2/src/compiler/view_compiler/view_binder.ts b/modules/angular2/src/compiler/view_compiler/view_binder.ts index 9d45c833e7..806ba523ed 100644 --- a/modules/angular2/src/compiler/view_compiler/view_binder.ts +++ b/modules/angular2/src/compiler/view_compiler/view_binder.ts @@ -39,6 +39,8 @@ import {CompileElement, CompileNode} from './compile_element'; export function bindView(view: CompileView, parsedTemplate: TemplateAst[]): void { var visitor = new ViewBinderVisitor(view); templateVisitAll(visitor, parsedTemplate); + view.pipes.forEach( + (pipe) => { bindPipeDestroyLifecycleCallbacks(pipe.meta, pipe.instance, pipe.view); }); } class ViewBinderVisitor implements TemplateAstVisitor { diff --git a/modules/angular2/src/compiler/view_compiler/view_builder.ts b/modules/angular2/src/compiler/view_compiler/view_builder.ts index 71ba87cdd6..c6abdb06e2 100644 --- a/modules/angular2/src/compiler/view_compiler/view_builder.ts +++ b/modules/angular2/src/compiler/view_compiler/view_builder.ts @@ -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 * as o from '../output/output_ast'; @@ -429,8 +429,6 @@ function createViewClass(view: CompileView, renderCompTypeVar: o.ReadVarExpr, ViewConstructorVars.parentInjector, ViewConstructorVars.declarationEl, ChangeDetectionStrategyEnum.fromValue(getChangeDetectionMode(view)), - o.literal(view.literalArrayCount), - o.literal(view.literalMapCount), nodeDebugInfosVar ]) .toStmt() @@ -600,4 +598,4 @@ function getChangeDetectionMode(view: CompileView): ChangeDetectionStrategy { mode = ChangeDetectionStrategy.CheckAlways; } return mode; -} \ No newline at end of file +} diff --git a/modules/angular2/src/core/change_detection/pipe_transform.dart b/modules/angular2/src/core/change_detection/pipe_transform.dart new file mode 100644 index 0000000000..cc3abbd0da --- /dev/null +++ b/modules/angular2/src/core/change_detection/pipe_transform.dart @@ -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 ...args): any; +} diff --git a/modules/angular2/src/core/change_detection/pipe_transform.ts b/modules/angular2/src/core/change_detection/pipe_transform.ts index 07cf19c376..298fadedee 100644 --- a/modules/angular2/src/core/change_detection/pipe_transform.ts +++ b/modules/angular2/src/core/change_detection/pipe_transform.ts @@ -17,11 +17,7 @@ * * @Pipe({name: 'repeat'}) * export class RepeatPipe implements PipeTransform { - * transform(value: any, args: any[] = []) { - * if (args.length == 0) { - * throw new Error('repeat pipe requires one argument'); - * } - * let times: number = args[0]; + * transform(value: any, times: number) { * return value.repeat(times); * } * } @@ -30,4 +26,4 @@ * 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; } diff --git a/modules/angular2/src/core/linker/view.ts b/modules/angular2/src/core/linker/view.ts index dd1001f531..002ea04ff8 100644 --- a/modules/angular2/src/core/linker/view.ts +++ b/modules/angular2/src/core/linker/view.ts @@ -70,9 +70,6 @@ export abstract class AppView { renderParent: AppView; 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 // change detection will fail. cdState: ChangeDetectorState = ChangeDetectorState.NeverChecked; @@ -96,16 +93,14 @@ export abstract class AppView { constructor(public clazz: any, public componentType: RenderComponentType, public type: ViewType, public locals: {[key: string]: any}, public viewUtils: ViewUtils, public parentInjector: Injector, public declarationAppElement: AppElement, - public cdMode: ChangeDetectionStrategy, literalArrayCacheSize: number, - literalMapCacheSize: number, public staticNodeDebugInfos: StaticNodeDebugInfo[]) { + public cdMode: ChangeDetectionStrategy, + public staticNodeDebugInfos: StaticNodeDebugInfo[]) { this.ref = new ViewRef_(this); if (type === ViewType.COMPONENT || type === ViewType.HOST) { this.renderer = viewUtils.renderComponent(componentType); } else { this.renderer = declarationAppElement.parentView.renderer; } - this._literalArrayCache = ListWrapper.createFixedSize(literalArrayCacheSize); - this._literalMapCache = ListWrapper.createFixedSize(literalMapCacheSize); } create(givenProjectableNodes: Array, rootSelectorOrNode: string | any): AppElement { @@ -269,6 +264,10 @@ export abstract class AppView { get changeDetectorRef(): ChangeDetectorRef { return this.ref; } + get parent(): AppView { + return isPresent(this.declarationAppElement) ? this.declarationAppElement.parentView : null; + } + get flatRootNodes(): any[] { return flattenNestedViewRenderNodes(this.rootNodesOrAppElements); } get lastRootNode(): any { @@ -360,28 +359,6 @@ export abstract class AppView { 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; } markPathToRootAsCheckOnce(): void { diff --git a/modules/angular2/src/core/linker/view_utils.ts b/modules/angular2/src/core/linker/view_utils.ts index 9f79e720e1..7ea8b10e35 100644 --- a/modules/angular2/src/core/linker/view_utils.ts +++ b/modules/angular2/src/core/linker/view_utils.ts @@ -10,7 +10,7 @@ import {ListWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {BaseException} from 'angular2/src/facade/exceptions'; import {AppElement} from './element'; 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 {RootRenderer, RenderComponentType, Renderer} from 'angular2/src/core/render/api'; import {APP_ID} from 'angular2/src/core/application_tokens'; @@ -158,3 +158,209 @@ export function mapLooseIdentical(m1: {[key: string]: V}, m2: {[key: string]: } return true; } + +export function castByValue(input: any, value: T): T { + return input; +} + +export function pureProxy1(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(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(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(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( + 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( + 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( + 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( + 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( + 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( + 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; + }; +} diff --git a/modules/angular2/test/common/pipes/async_pipe_spec.ts b/modules/angular2/test/common/pipes/async_pipe_spec.ts index cd30502f7d..75afc6dbcf 100644 --- a/modules/angular2/test/common/pipes/async_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/async_pipe_spec.ts @@ -208,14 +208,14 @@ export function main() { describe('null', () => { it('should return null when given null', () => { var pipe = new AsyncPipe(null); - expect(pipe.transform(null, [])).toEqual(null); + expect(pipe.transform(null)).toEqual(null); }); }); describe('other types', () => { it('should throw when given an invalid object', () => { var pipe = new AsyncPipe(null); - expect(() => pipe.transform("some bogus object", [])).toThrowError(); + expect(() => pipe.transform("some bogus object")).toThrowError(); }); }); }); diff --git a/modules/angular2/test/common/pipes/date_pipe_spec.ts b/modules/angular2/test/common/pipes/date_pipe_spec.ts index 24666b20d2..5d6c6598d4 100644 --- a/modules/angular2/test/common/pipes/date_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/date_pipe_spec.ts @@ -42,39 +42,39 @@ export function main() { if (browserDetection.supportsIntlApi) { describe("transform", () => { it('should format each component correctly', () => { - expect(pipe.transform(date, ['y'])).toEqual('2015'); - expect(pipe.transform(date, ['yy'])).toEqual('15'); - expect(pipe.transform(date, ['M'])).toEqual('6'); - expect(pipe.transform(date, ['MM'])).toEqual('06'); - expect(pipe.transform(date, ['MMM'])).toEqual('Jun'); - expect(pipe.transform(date, ['MMMM'])).toEqual('June'); - expect(pipe.transform(date, ['d'])).toEqual('15'); - expect(pipe.transform(date, ['E'])).toEqual('Mon'); - expect(pipe.transform(date, ['EEEE'])).toEqual('Monday'); - expect(pipe.transform(date, ['H'])).toEqual('21'); - expect(pipe.transform(date, ['j'])).toEqual('9 PM'); - expect(pipe.transform(date, ['m'])).toEqual('43'); - expect(pipe.transform(date, ['s'])).toEqual('11'); + expect(pipe.transform(date, 'y')).toEqual('2015'); + expect(pipe.transform(date, 'yy')).toEqual('15'); + expect(pipe.transform(date, 'M')).toEqual('6'); + expect(pipe.transform(date, 'MM')).toEqual('06'); + expect(pipe.transform(date, 'MMM')).toEqual('Jun'); + expect(pipe.transform(date, 'MMMM')).toEqual('June'); + expect(pipe.transform(date, 'd')).toEqual('15'); + expect(pipe.transform(date, 'E')).toEqual('Mon'); + expect(pipe.transform(date, 'EEEE')).toEqual('Monday'); + expect(pipe.transform(date, 'H')).toEqual('21'); + expect(pipe.transform(date, 'j')).toEqual('9 PM'); + expect(pipe.transform(date, 'm')).toEqual('43'); + expect(pipe.transform(date, 's')).toEqual('11'); }); it('should format common multi component patterns', () => { - expect(pipe.transform(date, ['yMEd'])).toEqual('Mon, 6/15/2015'); - expect(pipe.transform(date, ['MEd'])).toEqual('Mon, 6/15'); - expect(pipe.transform(date, ['MMMd'])).toEqual('Jun 15'); - expect(pipe.transform(date, ['yMMMMEEEEd'])).toEqual('Monday, June 15, 2015'); - expect(pipe.transform(date, ['jms'])).toEqual('9:43:11 PM'); - expect(pipe.transform(date, ['ms'])).toEqual('43:11'); + expect(pipe.transform(date, 'yMEd')).toEqual('Mon, 6/15/2015'); + expect(pipe.transform(date, 'MEd')).toEqual('Mon, 6/15'); + expect(pipe.transform(date, 'MMMd')).toEqual('Jun 15'); + expect(pipe.transform(date, 'yMMMMEEEEd')).toEqual('Monday, June 15, 2015'); + expect(pipe.transform(date, 'jms')).toEqual('9:43:11 PM'); + expect(pipe.transform(date, 'ms')).toEqual('43:11'); }); it('should format with pattern aliases', () => { - 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, ['fullDate'])).toEqual('Monday, 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, ['shortDate'])).toEqual('6/15/2015'); - expect(pipe.transform(date, ['mediumTime'])).toEqual('9:43:11 PM'); - expect(pipe.transform(date, ['shortTime'])).toEqual('9:43 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, 'fullDate')).toEqual('Monday, 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, 'shortDate')).toEqual('6/15/2015'); + expect(pipe.transform(date, 'mediumTime')).toEqual('9:43:11 PM'); + expect(pipe.transform(date, 'shortTime')).toEqual('9:43 PM'); }); }); } diff --git a/modules/angular2/test/common/pipes/i18n_plural_pipe_spec.ts b/modules/angular2/test/common/pipes/i18n_plural_pipe_spec.ts index 600c6a61b3..1737d6a58b 100644 --- a/modules/angular2/test/common/pipes/i18n_plural_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/i18n_plural_pipe_spec.ts @@ -26,33 +26,33 @@ export function main() { describe("transform", () => { 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.'); }); 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.'); }); 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.'); }); 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.'); }); it("should use 'other' if value is undefined", () => { var messageLength; - var val = pipe.transform(messageLength, [interpolatedMapping]); + var val = pipe.transform(messageLength, interpolatedMapping); expect(val).toEqual('There are messages, that is .'); }); it("should not support bad arguments", - () => { expect(() => pipe.transform(0, ['hey'])).toThrowError(); }); + () => { expect(() => pipe.transform(0, 'hey')).toThrowError(); }); }); }); diff --git a/modules/angular2/test/common/pipes/i18n_select_pipe_spec.ts b/modules/angular2/test/common/pipes/i18n_select_pipe_spec.ts index 6b0e27f934..ad32e8b251 100644 --- a/modules/angular2/test/common/pipes/i18n_select_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/i18n_select_pipe_spec.ts @@ -24,28 +24,28 @@ export function main() { describe("transform", () => { 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.'); }); 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.'); }); 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.'); }); it("should use 'other' if value is undefined", () => { var gender; - var val = pipe.transform(gender, [mapping]); + var val = pipe.transform(gender, mapping); expect(val).toEqual('Invite them.'); }); it("should not support bad arguments", - () => { expect(() => pipe.transform('male', ['hey'])).toThrowError(); }); + () => { expect(() => pipe.transform('male', 'hey')).toThrowError(); }); }); }); diff --git a/modules/angular2/test/common/pipes/number_pipe_spec.ts b/modules/angular2/test/common/pipes/number_pipe_spec.ts index c141a740cf..33d12fa335 100644 --- a/modules/angular2/test/common/pipes/number_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/number_pipe_spec.ts @@ -24,17 +24,17 @@ export function main() { describe("transform", () => { it('should return correct value for numbers', () => { - expect(pipe.transform(12345, [])).toEqual('12,345'); - expect(pipe.transform(123, ['.2'])).toEqual('123.00'); - expect(pipe.transform(1, ['3.'])).toEqual('001'); - expect(pipe.transform(1.1, ['3.4-5'])).toEqual('001.1000'); + expect(pipe.transform(12345)).toEqual('12,345'); + expect(pipe.transform(123, '.2')).toEqual('123.00'); + expect(pipe.transform(1, '3.')).toEqual('001'); + 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.1234, [])).toEqual('1.123'); + expect(pipe.transform(1.123456, '3.4-5')).toEqual('001.12346'); + expect(pipe.transform(1.1234)).toEqual('1.123'); }); 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", () => { it('should return correct value for numbers', () => { - expect(pipe.transform(1.23, [])).toEqual('123%'); - expect(pipe.transform(1.2, ['.2'])).toEqual('120.00%'); + expect(pipe.transform(1.23)).toEqual('123%'); + expect(pipe.transform(1.2, '.2')).toEqual('120.00%'); }); 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", () => { it('should return correct value for numbers', () => { - expect(pipe.transform(123, [])).toEqual('USD123'); - expect(pipe.transform(12, ['EUR', false, '.2'])).toEqual('EUR12.00'); + expect(pipe.transform(123)).toEqual('USD123'); + expect(pipe.transform(12, 'EUR', false, '.2')).toEqual('EUR12.00'); }); it("should not support other objects", - () => { expect(() => pipe.transform(new Object(), [])).toThrowError(); }); + () => { expect(() => pipe.transform(new Object())).toThrowError(); }); }); }); } diff --git a/modules/angular2/test/common/pipes/replace_pipe_spec.ts b/modules/angular2/test/common/pipes/replace_pipe_spec.ts index fee33b2168..652316a1bd 100644 --- a/modules/angular2/test/common/pipes/replace_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/replace_pipe_spec.ts @@ -31,34 +31,34 @@ export function main() { describe("transform", () => { it("should not support input other than strings and numbers", () => { - expect(() => pipe.transform({}, ["Douglas", "Hugh"])).toThrow(); - expect(() => pipe.transform([1, 2, 3], ["Douglas", "Hugh"])).toThrow(); + expect(() => pipe.transform({}, "Douglas", "Hugh")).toThrow(); + expect(() => pipe.transform([1, 2, 3], "Douglas", "Hugh")).toThrow(); }); it("should not support patterns other than strings and regular expressions", () => { - expect(() => pipe.transform(str, [{}, "Hugh"])).toThrow(); - expect(() => pipe.transform(str, [null, "Hugh"])).toThrow(); - expect(() => pipe.transform(str, [123, "Hugh"])).toThrow(); + expect(() => pipe.transform(str, {}, "Hugh")).toThrow(); + expect(() => pipe.transform(str, null, "Hugh")).toThrow(); + expect(() => pipe.transform(str, 123, "Hugh")).toThrow(); }); it("should not support replacements other than strings and functions", () => { - expect(() => pipe.transform(str, ["Douglas", {}])).toThrow(); - expect(() => pipe.transform(str, ["Douglas", null])).toThrow(); - expect(() => pipe.transform(str, ["Douglas", 123])).toThrow(); + expect(() => pipe.transform(str, "Douglas", {})).toThrow(); + expect(() => pipe.transform(str, "Douglas", null)).toThrow(); + expect(() => pipe.transform(str, "Douglas", 123)).toThrow(); }); 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 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(result2).toEqual("Dougl_s Ad_ms"); diff --git a/modules/angular2/test/common/pipes/slice_pipe_spec.ts b/modules/angular2/test/common/pipes/slice_pipe_spec.ts index dab2236c3f..ac04fcf624 100644 --- a/modules/angular2/test/common/pipes/slice_pipe_spec.ts +++ b/modules/angular2/test/common/pipes/slice_pipe_spec.ts @@ -42,47 +42,47 @@ export function main() { 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(str, [3])).toEqual('wxyz'); + expect(pipe.transform(list, 3)).toEqual([4, 5]); + expect(pipe.transform(str, 3)).toEqual('wxyz'); }); 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(str, [-3])).toEqual('xyz'); + expect(pipe.transform(list, -3)).toEqual([3, 4, 5]); + expect(pipe.transform(str, -3)).toEqual('xyz'); }); 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(str, [1, 3])).toEqual('uv'); + expect(pipe.transform(list, 1, 3)).toEqual([2, 3]); + 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', () => { - expect(pipe.transform(list, [-4, -2])).toEqual([2, 3]); - expect(pipe.transform(str, [-4, -2])).toEqual('wx'); + expect(pipe.transform(list, -4, -2)).toEqual([2, 3]); + expect(pipe.transform(str, -4, -2)).toEqual('wx'); }); it('should return an empty value if START is greater than END', () => { - expect(pipe.transform(list, [4, 2])).toEqual([]); - expect(pipe.transform(str, [4, 2])).toEqual(''); + expect(pipe.transform(list, 4, 2)).toEqual([]); + expect(pipe.transform(str, 4, 2)).toEqual(''); }); it('should return an empty value if START greater than input length', () => { - expect(pipe.transform(list, [99])).toEqual([]); - expect(pipe.transform(str, [99])).toEqual(''); + expect(pipe.transform(list, 99)).toEqual([]); + expect(pipe.transform(str, 99)).toEqual(''); }); // Makes Edge to disconnect when running the full unit test campaign // TODO: remove when issue is solved: https://github.com/angular/angular/issues/4756 if (!browserDetection.isEdge) { 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(str, [-99])).toEqual('tuvwxyz'); + expect(pipe.transform(list, -99)).toEqual([1, 2, 3, 4, 5]); + expect(pipe.transform(str, -99)).toEqual('tuvwxyz'); }); 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]); }); } diff --git a/modules/angular2/test/compiler/output/dart_emitter_spec.ts b/modules/angular2/test/compiler/output/dart_emitter_spec.ts index ba1a68a274..264838f9ee 100644 --- a/modules/angular2/test/compiler/output/dart_emitter_spec.ts +++ b/modules/angular2/test/compiler/output/dart_emitter_spec.ts @@ -103,6 +103,11 @@ export function main() { .callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')]) .toStmt())) .toEqual('observable.listen(listener);'); + + expect( + emitStmt( + o.variable('fn').callMethod(o.BuiltinMethod.bind, [o.variable('someObj')]).toStmt())) + .toEqual('fn;'); }); it('should support literals', () => { diff --git a/modules/angular2/test/compiler/output/js_emitter_spec.ts b/modules/angular2/test/compiler/output/js_emitter_spec.ts index ced95b60f0..49d5bd6cff 100644 --- a/modules/angular2/test/compiler/output/js_emitter_spec.ts +++ b/modules/angular2/test/compiler/output/js_emitter_spec.ts @@ -95,6 +95,11 @@ export function main() { .callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')]) .toStmt())) .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', () => { diff --git a/modules/angular2/test/compiler/output/output_emitter_spec.ts b/modules/angular2/test/compiler/output/output_emitter_spec.ts index b476d922dc..faae79fe3d 100644 --- a/modules/angular2/test/compiler/output/output_emitter_spec.ts +++ b/modules/angular2/test/compiler/output/output_emitter_spec.ts @@ -88,8 +88,12 @@ export function main() { expect(expressions['concatedArray']).toEqual([0, 1]); expect(expressions['invokeMethodExternalInstance']) .toEqual({'data': 'someValue', 'param': 'someParam'}); + expect(expressions['invokeMethodExternalInstanceViaBind']) + .toEqual({'data': 'someValue', 'param': 'someParam'}); expect(expressions['invokeMethodDynamicInstance']) .toEqual({'data': 'someValue', 'dynamicProp': 'dynamicValue', 'param': 'someParam'}); + expect(expressions['invokeMethodDynamicInstanceViaBind']) + .toEqual({'data': 'someValue', 'dynamicProp': 'dynamicValue', 'param': 'someParam'}); }); it('should support conditionals', () => { diff --git a/modules/angular2/test/compiler/output/output_emitter_util.ts b/modules/angular2/test/compiler/output/output_emitter_util.ts index b4ec8a4e70..d48484df66 100644 --- a/modules/angular2/test/compiler/output/output_emitter_util.ts +++ b/modules/angular2/test/compiler/output/output_emitter_util.ts @@ -116,10 +116,24 @@ var _getExpressionsStmts: o.Statement[] = [ 'invokeMethodExternalInstance', 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', 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', o.literalArr([o.literal(0)]) diff --git a/modules/angular2/test/compiler/output/ts_emitter_spec.ts b/modules/angular2/test/compiler/output/ts_emitter_spec.ts index 2d97bc1d9c..7dcaaa4a59 100644 --- a/modules/angular2/test/compiler/output/ts_emitter_spec.ts +++ b/modules/angular2/test/compiler/output/ts_emitter_spec.ts @@ -96,6 +96,11 @@ export function main() { .callMethod(o.BuiltinMethod.SubscribeObservable, [o.variable('listener')]) .toStmt())) .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', () => { diff --git a/modules/angular2/test/core/linker/change_detection_integration_spec.ts b/modules/angular2/test/core/linker/change_detection_integration_spec.ts index becf958cf8..4275c44422 100644 --- a/modules/angular2/test/core/linker/change_detection_integration_spec.ts +++ b/modules/angular2/test/core/linker/change_detection_integration_spec.ts @@ -460,6 +460,13 @@ export function main() { })); 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); ctx.componentInstance.name = 'value'; ctx.detectChanges(false); @@ -491,6 +498,56 @@ export function main() { 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(`
`, 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', () => { @@ -1014,6 +1071,7 @@ const ALL_DIRECTIVES = CONST_EXPR([ const ALL_PIPES = CONST_EXPR([ forwardRef(() => CountingPipe), + forwardRef(() => CountingImpurePipe), forwardRef(() => MultiArgPipe), forwardRef(() => PipeWithOnDestroy), forwardRef(() => IdentityPipe), @@ -1086,7 +1144,13 @@ class DirectiveLog { @Pipe({name: 'countingPipe'}) class CountingPipe implements PipeTransform { 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'}) @@ -1095,27 +1159,22 @@ class PipeWithOnDestroy implements PipeTransform, OnDestroy { ngOnDestroy() { this.directiveLog.add('pipeWithOnDestroy', 'ngOnDestroy'); } - transform(value, args = null) { return null; } + transform(value) { return null; } } @Pipe({name: 'identityPipe'}) class IdentityPipe implements PipeTransform { - transform(value, args = null) { return value; } + transform(value) { return value; } } @Pipe({name: 'wrappedPipe'}) class WrappedPipe implements PipeTransform { - transform(value, args = null) { return WrappedValue.wrap(value); } + transform(value) { return WrappedValue.wrap(value); } } @Pipe({name: 'multiArgPipe'}) class MultiArgPipe implements PipeTransform { - transform(value, args = null) { - var arg1 = args[0]; - var arg2 = args[1]; - var arg3 = args.length > 2 ? args[2] : 'default'; - return `${value} ${arg1} ${arg2} ${arg3}`; - } + transform(value, arg1, arg2, arg3 = 'default') { return `${value} ${arg1} ${arg2} ${arg3}`; } } @Component({selector: 'test-cmp', template: '', directives: ALL_DIRECTIVES, pipes: ALL_PIPES}) diff --git a/modules/angular2/test/core/linker/integration_spec.ts b/modules/angular2/test/core/linker/integration_spec.ts index 5d677f5be9..bcbbe50ef2 100644 --- a/modules/angular2/test/core/linker/integration_spec.ts +++ b/modules/angular2/test/core/linker/integration_spec.ts @@ -2136,7 +2136,7 @@ class SomeViewport { @Pipe({name: 'double'}) class DoublePipe implements PipeTransform, OnDestroy { ngOnDestroy() {} - transform(value, args = null) { return `${value}${value}`; } + transform(value) { return `${value}${value}`; } } @Directive({selector: '[emitter]', outputs: ['event']}) diff --git a/modules/angular2/test/core/linker/regression_integration_spec.ts b/modules/angular2/test/core/linker/regression_integration_spec.ts index 1570e92c24..0851a6efef 100644 --- a/modules/angular2/test/core/linker/regression_integration_spec.ts +++ b/modules/angular2/test/core/linker/regression_integration_spec.ts @@ -152,10 +152,10 @@ class MyComp { @Pipe({name: 'somePipe', pure: true}) class PlatformPipe implements PipeTransform { - transform(value: any, args: any[]): any { return 'somePlatformPipe'; } + transform(value: any): any { return 'somePlatformPipe'; } } @Pipe({name: 'somePipe', pure: true}) class CustomPipe implements PipeTransform { - transform(value: any, args: any[]): any { return 'someCustomPipe'; } + transform(value: any): any { return 'someCustomPipe'; } } diff --git a/modules/angular2/test/core/linker/view_injector_integration_spec.ts b/modules/angular2/test/core/linker/view_injector_integration_spec.ts index 62ce924fad..7ae2ff2b3d 100644 --- a/modules/angular2/test/core/linker/view_injector_integration_spec.ts +++ b/modules/angular2/test/core/linker/view_injector_integration_spec.ts @@ -232,38 +232,38 @@ class PushComponentNeedsChangeDetectorRef { } @Pipe({name: 'purePipe', pure: true}) -class PurePipe { +class PurePipe implements PipeTransform { constructor() {} - transform(value: any, args: any[] = null): any { return this; } + transform(value: any): any { return this; } } @Pipe({name: 'impurePipe', pure: false}) -class ImpurePipe { +class ImpurePipe implements PipeTransform { constructor() {} - transform(value: any, args: any[] = null): any { return this; } + transform(value: any): any { return this; } } @Pipe({name: 'pipeNeedsChangeDetectorRef'}) class PipeNeedsChangeDetectorRef { constructor(public changeDetectorRef: ChangeDetectorRef) {} - transform(value: any, args: any[] = null): any { return this; } + transform(value: any): any { return this; } } @Pipe({name: 'pipeNeedsService'}) export class PipeNeedsService implements PipeTransform { service: any; 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'}) export class DuplicatePipe1 implements PipeTransform { - transform(value: any, args: any[] = null): any { return this; } + transform(value: any): any { return this; } } @Pipe({name: 'duplicatePipe'}) export class DuplicatePipe2 implements PipeTransform { - transform(value: any, args: any[] = null): any { return this; } + transform(value: any): any { return this; } } @Component({selector: 'root'}) @@ -654,7 +654,7 @@ export function main() { it('should cache pure pipes', fakeAsync(() => { var el = createComp( - '
', + '
', tcb); var purePipe1 = el.children[0].inject(SimpleDirective).value; var purePipe2 = el.children[1].inject(SimpleDirective).value; diff --git a/tools/public_api_guard/public_api_spec.ts b/tools/public_api_guard/public_api_spec.ts index c6284a7df9..ebdea575d7 100644 --- a/tools/public_api_guard/public_api_spec.ts +++ b/tools/public_api_guard/public_api_spec.ts @@ -575,7 +575,7 @@ const COMMON = [ 'AsyncPipe', 'AsyncPipe.constructor(_ref:ChangeDetectorRef)', 'AsyncPipe.ngOnDestroy():void', - 'AsyncPipe.transform(obj:Observable|Promise|EventEmitter, args:any[]):any', + 'AsyncPipe.transform(obj:Observable|Promise|EventEmitter):any', 'CheckboxControlValueAccessor', 'CheckboxControlValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)', 'CheckboxControlValueAccessor.onChange:any', @@ -610,12 +610,12 @@ const COMMON = [ 'ControlValueAccessor.registerOnTouched(fn:any):void', 'ControlValueAccessor.writeValue(obj:any):void', 'CurrencyPipe', - 'CurrencyPipe.transform(value:any, args:any[]):string', + 'CurrencyPipe.transform(value:any, currencyCode:string, symbolDisplay:boolean, digits:string):string', 'DatePipe', 'DatePipe.supports(obj:any):boolean', - 'DatePipe.transform(value:any, args:any[]):string', + 'DatePipe.transform(value:any, pattern:string):string', 'DecimalPipe', - 'DecimalPipe.transform(value:any, args:any[]):string', + 'DecimalPipe.transform(value:any, digits:string):string', 'DefaultValueAccessor', 'DefaultValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)', 'DefaultValueAccessor.onChange:any', @@ -636,13 +636,13 @@ const COMMON = [ 'FormBuilder.control(value:Object, validator:ValidatorFn, asyncValidator:AsyncValidatorFn):Control', 'FormBuilder.group(controlsConfig:{[key:string]:any}, extra:{[key:string]:any}):ControlGroup', 'I18nPluralPipe', - 'I18nPluralPipe.transform(value:number, args:any[]):string', + 'I18nPluralPipe.transform(value:number, pluralMap:{[count:string]:string}):string', 'I18nSelectPipe', - 'I18nSelectPipe.transform(value:string, args:any[]):string', + 'I18nSelectPipe.transform(value:string, mapping:{[key:string]:string}):string', 'JsonPipe', - 'JsonPipe.transform(value:any, args:any[]):string', + 'JsonPipe.transform(value:any):string', 'LowerCasePipe', - 'LowerCasePipe.transform(value:string, args:any[]):string', + 'LowerCasePipe.transform(value:string):string', 'MaxLengthValidator', 'MaxLengthValidator.constructor(maxLength:string)', 'MaxLengthValidator.validate(c:AbstractControl):{[key:string]:any}', @@ -790,9 +790,9 @@ const COMMON = [ 'PatternValidator.constructor(pattern:string)', 'PatternValidator.validate(c:AbstractControl):{[key:string]:any}', 'PercentPipe', - 'PercentPipe.transform(value:any, args:any[]):string', + 'PercentPipe.transform(value:any, digits:string):string', 'ReplacePipe', - 'ReplacePipe.transform(value:any, args:any[]):any', + 'ReplacePipe.transform(value:any, pattern:string|RegExp, replacement:Function|string):any', 'RequiredValidator', 'SelectControlValueAccessor', 'SelectControlValueAccessor.constructor(_renderer:Renderer, _elementRef:ElementRef)', @@ -803,9 +803,9 @@ const COMMON = [ 'SelectControlValueAccessor.value:any', 'SelectControlValueAccessor.writeValue(value:any):void', 'SlicePipe', - 'SlicePipe.transform(value:any, args:any[]):any', + 'SlicePipe.transform(value:any, start:number, end:number):any', 'UpperCasePipe', - 'UpperCasePipe.transform(value:string, args:any[]):string', + 'UpperCasePipe.transform(value:string):string', 'Validator', 'Validator.validate(c:AbstractControl):{[key:string]:any}', 'Validators',