From d6626309fd8a25ce22ef76adc7ca2c3ee931ab37 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Mon, 25 Apr 2016 07:53:49 -0700 Subject: [PATCH 1/3] Revert "fix(compiler): only call pure pipes if their input changed." This reverts commit 8db62151d27f6f698c30f0944610ef92c66fcb5a. --- .../compiler/view_compiler/compile_view.ts | 19 +------- .../view_compiler/expression_converter.ts | 9 ++-- modules/angular2/src/core/linker/view.ts | 16 +------ .../change_detection_integration_spec.ts | 43 ------------------- 4 files changed, 9 insertions(+), 78 deletions(-) diff --git a/modules/angular2/src/compiler/view_compiler/compile_view.ts b/modules/angular2/src/compiler/view_compiler/compile_view.ts index 1c1bf99ed2a3..41fad4304479 100644 --- a/modules/angular2/src/compiler/view_compiler/compile_view.ts +++ b/modules/angular2/src/compiler/view_compiler/compile_view.ts @@ -124,7 +124,7 @@ export class CompileView implements NameResolver { } } - callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression { + createPipe(name: string): o.Expression { var pipeMeta: CompilePipeMetadata = null; for (var i = this.pipeMetas.length - 1; i >= 0; i--) { var localPipeMeta = this.pipeMetas[i]; @@ -139,7 +139,6 @@ export class CompileView implements NameResolver { } var pipeFieldName = pipeMeta.pure ? `_pipe_${name}` : `_pipe_${name}_${this.pipes.size}`; var pipeExpr = this.pipes.get(pipeFieldName); - var pipeFieldCacheProp = o.THIS_EXPR.prop(`${pipeFieldName}_cache`); if (isBlank(pipeExpr)) { var deps = pipeMeta.type.diDeps.map((diDep) => { if (diDep.token.equalsTo(identifierToken(Identifiers.ChangeDetectorRef))) { @@ -149,12 +148,6 @@ export class CompileView implements NameResolver { }); this.fields.push( new o.ClassField(pipeFieldName, o.importType(pipeMeta.type), [o.StmtModifier.Private])); - if (pipeMeta.pure) { - this.fields.push(new o.ClassField(pipeFieldCacheProp.name, null, [o.StmtModifier.Private])); - this.createMethod.addStmt(o.THIS_EXPR.prop(pipeFieldCacheProp.name) - .set(o.importExpr(Identifiers.uninitialized)) - .toStmt()); - } this.createMethod.resetDebugInfo(null, null); this.createMethod.addStmt(o.THIS_EXPR.prop(pipeFieldName) .set(o.importExpr(pipeMeta.type).instantiate(deps)) @@ -163,15 +156,7 @@ export class CompileView implements NameResolver { this.pipes.set(pipeFieldName, pipeExpr); bindPipeDestroyLifecycleCallbacks(pipeMeta, pipeExpr, this); } - var callPipeExpr: o.Expression = pipeExpr.callMethod('transform', [input, o.literalArr(args)]); - if (pipeMeta.pure) { - callPipeExpr = - o.THIS_EXPR.callMethod( - 'checkPurePipe', - [o.literal(this.literalArrayCount++), o.literalArr([input].concat(args))]) - .conditional(pipeFieldCacheProp.set(callPipeExpr), pipeFieldCacheProp); - } - return callPipeExpr; + return pipeExpr; } getVariable(name: string): o.Expression { diff --git a/modules/angular2/src/compiler/view_compiler/expression_converter.ts b/modules/angular2/src/compiler/view_compiler/expression_converter.ts index b3d512cc5f13..c50f19595bc1 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 { - callPipe(name: string, input: o.Expression, args: o.Expression[]): o.Expression; + createPipe(name: string): o.Expression; getVariable(name: string): o.Expression; createLiteralArray(values: o.Expression[]): o.Expression; createLiteralMap(values: Array>): o.Expression; @@ -132,12 +132,13 @@ 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 pipeResult = this._nameResolver.callPipe(ast.name, input, args); this.needsValueUnwrapper = true; - return convertToStatementIfNeeded(mode, - this._valueUnwrapper.callMethod('unwrap', [pipeResult])); + return convertToStatementIfNeeded( + mode, this._valueUnwrapper.callMethod( + 'unwrap', [pipeInstance.callMethod('transform', [input, o.literalArr(args)])])); } visitFunctionCall(ast: cdAst.FunctionCall, mode: _Mode): any { return convertToStatementIfNeeded(mode, ast.target.visit(this, _Mode.Expression) diff --git a/modules/angular2/src/core/linker/view.ts b/modules/angular2/src/core/linker/view.ts index 1f97473c2ec8..dd1001f53105 100644 --- a/modules/angular2/src/core/linker/view.ts +++ b/modules/angular2/src/core/linker/view.ts @@ -360,23 +360,11 @@ export abstract class AppView { this.viewContainerElement = null; } - checkPurePipe(id: number, newArgs: any[]): boolean { - var prevArgs = this._literalArrayCache[id]; - var newPresent = isPresent(newArgs); - var prevPresent = isPresent(prevArgs); - if (newPresent !== prevPresent || (newPresent && !arrayLooseIdentical(prevArgs, newArgs))) { - this._literalArrayCache[id] = newArgs; - return true; - } else { - return false; - } - } - literalArray(id: number, value: any[]): any[] { + var prevValue = this._literalArrayCache[id]; if (isBlank(value)) { return value; } - var prevValue = this._literalArrayCache[id]; if (isBlank(prevValue) || !arrayLooseIdentical(prevValue, value)) { prevValue = this._literalArrayCache[id] = value; } @@ -384,10 +372,10 @@ export abstract class AppView { } literalMap(id: number, value: {[key: string]: any}): {[key: string]: any} { + var prevValue = this._literalMapCache[id]; if (isBlank(value)) { return value; } - var prevValue = this._literalMapCache[id]; if (isBlank(prevValue) || !mapLooseIdentical(prevValue, value)) { prevValue = this._literalMapCache[id] = value; } 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 c15b10437b3f..becf958cf85a 100644 --- a/modules/angular2/test/core/linker/change_detection_integration_spec.ts +++ b/modules/angular2/test/core/linker/change_detection_integration_spec.ts @@ -491,42 +491,6 @@ 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 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', () => { @@ -1050,7 +1014,6 @@ const ALL_DIRECTIVES = CONST_EXPR([ const ALL_PIPES = CONST_EXPR([ forwardRef(() => CountingPipe), - forwardRef(() => CountingImpurePipe), forwardRef(() => MultiArgPipe), forwardRef(() => PipeWithOnDestroy), forwardRef(() => IdentityPipe), @@ -1126,12 +1089,6 @@ class CountingPipe implements PipeTransform { transform(value, args = null) { return `${value} state:${this.state ++}`; } } -@Pipe({name: 'countingImpurePipe', pure: false}) -class CountingImpurePipe implements PipeTransform { - state: number = 0; - transform(value, args = null) { return `${value} state:${this.state ++}`; } -} - @Pipe({name: 'pipeWithOnDestroy'}) class PipeWithOnDestroy implements PipeTransform, OnDestroy { constructor(private directiveLog: DirectiveLog) {} From 152a117d5c27e56d1b32d69df2f69d34b94c0760 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Fri, 22 Apr 2016 15:33:32 -0700 Subject: [PATCH 2/3] 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. --- .../angular2/src/common/pipes/async_pipe.ts | 13 +- .../angular2/src/common/pipes/date_pipe.ts | 3 +- .../src/common/pipes/i18n_plural_pipe.ts | 3 +- .../src/common/pipes/i18n_select_pipe.ts | 3 +- .../angular2/src/common/pipes/json_pipe.ts | 2 +- .../src/common/pipes/lowercase_pipe.ts | 2 +- .../angular2/src/common/pipes/number_pipe.ts | 13 +- .../angular2/src/common/pipes/replace_pipe.ts | 18 +- .../angular2/src/common/pipes/slice_pipe.ts | 8 +- .../src/common/pipes/uppercase_pipe.ts | 2 +- modules/angular2/src/compiler/identifiers.ts | 39 +++- .../src/compiler/output/abstract_emitter.ts | 5 + .../compiler/output/abstract_js_emitter.ts | 3 + .../src/compiler/output/dart_emitter.ts | 3 + .../src/compiler/output/interpretive_view.ts | 3 +- .../src/compiler/output/output_ast.ts | 3 +- .../src/compiler/output/output_interpreter.ts | 9 + .../src/compiler/output/ts_emitter.ts | 3 + .../compiler/view_compiler/compile_element.ts | 8 +- .../compiler/view_compiler/compile_pipe.ts | 76 +++++++ .../compiler/view_compiler/compile_query.ts | 4 +- .../compiler/view_compiler/compile_view.ts | 94 ++++---- .../view_compiler/expression_converter.ts | 8 +- .../view_compiler/lifecycle_binder.ts | 6 +- .../src/compiler/view_compiler/util.ts | 36 ++- .../src/compiler/view_compiler/view_binder.ts | 2 + .../compiler/view_compiler/view_builder.ts | 6 +- .../core/change_detection/pipe_transform.dart | 33 +++ .../core/change_detection/pipe_transform.ts | 8 +- modules/angular2/src/core/linker/view.ts | 35 +-- .../angular2/src/core/linker/view_utils.ts | 208 +++++++++++++++++- .../test/common/pipes/async_pipe_spec.ts | 4 +- .../test/common/pipes/date_pipe_spec.ts | 54 ++--- .../common/pipes/i18n_plural_pipe_spec.ts | 12 +- .../common/pipes/i18n_select_pipe_spec.ts | 10 +- .../test/common/pipes/number_pipe_spec.ts | 26 +-- .../test/common/pipes/replace_pipe_spec.ts | 26 +-- .../test/common/pipes/slice_pipe_spec.ts | 30 +-- .../test/compiler/output/dart_emitter_spec.ts | 5 + .../test/compiler/output/js_emitter_spec.ts | 5 + .../compiler/output/output_emitter_spec.ts | 4 + .../compiler/output/output_emitter_util.ts | 14 ++ .../test/compiler/output/ts_emitter_spec.ts | 5 + .../change_detection_integration_spec.ts | 79 ++++++- .../test/core/linker/integration_spec.ts | 2 +- .../linker/regression_integration_spec.ts | 4 +- .../linker/view_injector_integration_spec.ts | 18 +- tools/public_api_guard/public_api_spec.ts | 24 +- 48 files changed, 698 insertions(+), 283 deletions(-) create mode 100644 modules/angular2/src/compiler/view_compiler/compile_pipe.ts create mode 100644 modules/angular2/src/core/change_detection/pipe_transform.dart diff --git a/modules/angular2/src/common/pipes/async_pipe.ts b/modules/angular2/src/common/pipes/async_pipe.ts index 9131cfb14795..b91130c693a0 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 27f38308a662..9ebab2e54712 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 0501441e3ab7..0959931b4815 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 04722d657fd8..b5a627303de5 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 99bcf97b7fe2..63b251915fd3 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 3fe106905434..ddc27a9b9aaf 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 d26bd53128fe..2aaef1b3bcfd 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 de3c0e9f7390..bfc643b17fd4 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 37d490da52c6..2e55b63b7430 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 f91d2ce460c0..37cb97dcd538 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 453ed25ce438..c07d96b5afcb 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 5ea387d79aef..c84d7809ff80 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 9b85f356e552..1708c8ab7793 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 9561a02efb55..6abb8918cf6a 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 26b61766eb7a..f00368d32b71 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 c4e7366d7a1a..3d41540f42b3 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 3451a3d22c4a..2b9ec6bc0bd7 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 2edc5e81a223..960372b54053 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 6c5b0446d5ed..020226f17f04 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 000000000000..4c62c93133ac --- /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 64c8b40c5272..63a88e4e8205 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 41fad4304479..c0c414226e03 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 c50f19595bc1..b94d077ae5c4 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 a70428190eeb..154b4688750b 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 fb589bffa18e..94d76ba2f942 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 9d45c833e71e..806ba523ed71 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 71ba87cdd68a..c6abdb06e25c 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 000000000000..cc3abbd0da6f --- /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 07cf19c376b6..298fadedeea4 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 dd1001f53105..002ea04ff81f 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 9f79e720e1a0..7ea8b10e35f5 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 cd30502f7d23..75afc6dbcfe9 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 24666b20d2b2..5d6c6598d459 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 600c6a61b38b..1737d6a58bcc 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 6b0e27f934a6..ad32e8b25189 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 c141a740cf98..33d12fa3357c 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 fee33b2168d2..652316a1bdb3 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 dab2236c3fd3..ac04fcf6247d 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 ba1a68a2744c..264838f9ee72 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 ced95b60f0bc..49d5bd6cff05 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 b476d922dccb..faae79fe3d15 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 b4ec8a4e7099..d48484df6604 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 2d97bc1d9cb4..7dcaaa4a59da 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 becf958cf85a..4275c4442208 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 5d677f5be9d0..bcbbe50ef2a9 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 1570e92c246f..0851a6efef20 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 62ce924fada9..7ae2ff2b3d3c 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 c6284a7df9d2..ebdea575d74b 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', From bd0a0582d972ddde9aa308b8b35fa20ae16d7ced Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Mon, 25 Apr 2016 08:39:53 -0700 Subject: [PATCH 3/3] fix(compiler): use DI order for change detection order. --- .../angular2/src/compiler/provider_parser.ts | 2 +- modules/angular2/src/compiler/template_ast.ts | 10 +++- .../test/compiler/template_parser_spec.ts | 16 +++++- .../change_detection_integration_spec.ts | 56 ++++++++++++++++++- 4 files changed, 78 insertions(+), 6 deletions(-) diff --git a/modules/angular2/src/compiler/provider_parser.ts b/modules/angular2/src/compiler/provider_parser.ts index 5a9788a21118..ff618cc07d69 100644 --- a/modules/angular2/src/compiler/provider_parser.ts +++ b/modules/angular2/src/compiler/provider_parser.ts @@ -109,7 +109,7 @@ export class ProviderElementContext { var sortedProviderTypes = this._transformedProviders.values().map(provider => provider.token.identifier); var sortedDirectives = ListWrapper.clone(this._directiveAsts); - ListWrapper.sort(this._directiveAsts, + ListWrapper.sort(sortedDirectives, (dir1, dir2) => sortedProviderTypes.indexOf(dir1.directive.type) - sortedProviderTypes.indexOf(dir2.directive.type)); return sortedDirectives; diff --git a/modules/angular2/src/compiler/template_ast.ts b/modules/angular2/src/compiler/template_ast.ts index 558956010a5e..f328131649ef 100644 --- a/modules/angular2/src/compiler/template_ast.ts +++ b/modules/angular2/src/compiler/template_ast.ts @@ -118,9 +118,13 @@ export class ElementAst implements TemplateAst { * Get the component associated with this element, if any. */ getComponent(): CompileDirectiveMetadata { - return this.directives.length > 0 && this.directives[0].directive.isComponent ? - this.directives[0].directive : - null; + for (var i = 0; i < this.directives.length; i++) { + var dirAst = this.directives[i]; + if (dirAst.directive.isComponent) { + return dirAst.directive; + } + } + return null; } } diff --git a/modules/angular2/test/compiler/template_parser_spec.ts b/modules/angular2/test/compiler/template_parser_spec.ts index df266448bbdc..9e4ecd80dc6f 100644 --- a/modules/angular2/test/compiler/template_parser_spec.ts +++ b/modules/angular2/test/compiler/template_parser_spec.ts @@ -633,7 +633,7 @@ export function main() { `Mixing multi and non multi provider is not possible for token service0 ("[ERROR ->]
"): TestComp@0:0`); }); - it('should sort providers and directives by their DI order', () => { + it('should sort providers by their DI order', () => { var provider0 = createProvider('service0', {deps: ['type:[dir2]']}); var provider1 = createProvider('service1'); var dir2 = createDir('[dir2]', {deps: ['service1']}); @@ -646,6 +646,20 @@ export function main() { expect(elAst.providers[3].providers).toEqual([provider0]); }); + it('should sort directives by their DI order', () => { + var dir0 = createDir('[dir0]', {deps: ['type:my-comp']}); + var dir1 = createDir('[dir1]', {deps: ['type:[dir0]']}); + var dir2 = createDir('[dir2]', {deps: ['type:[dir1]']}); + var comp = createDir('my-comp'); + var elAst: ElementAst = + parse('', [comp, dir2, dir0, dir1])[0]; + expect(elAst.providers.length).toBe(4); + expect(elAst.directives[0].directive).toBe(comp); + expect(elAst.directives[1].directive).toBe(dir0); + expect(elAst.directives[2].directive).toBe(dir1); + expect(elAst.directives[3].directive).toBe(dir2); + }); + it('should mark directives and dependencies of directives as eager', () => { var provider0 = createProvider('service0'); var provider1 = createProvider('service1'); 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 4275c4442208..20386313e9ed 100644 --- a/modules/angular2/test/core/linker/change_detection_integration_spec.ts +++ b/modules/angular2/test/core/linker/change_detection_integration_spec.ts @@ -1056,6 +1056,18 @@ export function main() { expect(renderLog.log).toEqual([]); })); }); + + describe('multi directive order', () => { + it('should follow the DI order for the same element', fakeAsync(() => { + var ctx = + createCompFixture('
'); + + ctx.detectChanges(false); + ctx.destroy(); + + expect(directiveLog.filter(['set'])).toEqual(['0.set', '1.set', '2.set']); + })); + }); }); } @@ -1066,7 +1078,10 @@ const ALL_DIRECTIVES = CONST_EXPR([ forwardRef(() => TestLocals), forwardRef(() => CompWithRef), forwardRef(() => EmitterDirective), - forwardRef(() => PushComp) + forwardRef(() => PushComp), + forwardRef(() => OrderCheckDirective2), + forwardRef(() => OrderCheckDirective0), + forwardRef(() => OrderCheckDirective1), ]); const ALL_PIPES = CONST_EXPR([ @@ -1296,6 +1311,45 @@ class TestDirective implements OnInit, DoCheck, OnChanges, AfterContentInit, Aft } } +@Directive({selector: '[orderCheck0]'}) +class OrderCheckDirective0 { + private _name: string; + + @Input('orderCheck0') + set name(value: string) { + this._name = value; + this.log.add(this._name, 'set'); + } + + constructor(public log: DirectiveLog) {} +} + +@Directive({selector: '[orderCheck1]'}) +class OrderCheckDirective1 { + private _name: string; + + @Input('orderCheck1') + set name(value: string) { + this._name = value; + this.log.add(this._name, 'set'); + } + + constructor(public log: DirectiveLog, _check0: OrderCheckDirective0) {} +} + +@Directive({selector: '[orderCheck2]'}) +class OrderCheckDirective2 { + private _name: string; + + @Input('orderCheck2') + set name(value: string) { + this._name = value; + this.log.add(this._name, 'set'); + } + + constructor(public log: DirectiveLog, _check1: OrderCheckDirective1) {} +} + @Directive({selector: '[testLocals]'}) class TestLocals { constructor(templateRef: TemplateRef, vcRef: ViewContainerRef) {