=
- CONST_EXPR([NgFor, NgIf, NgNonBindable, NgSwitch, NgSwitchWhen, NgSwitchDefault]);
+ CONST_EXPR([NgClass, NgFor, NgIf, NgNonBindable, NgSwitch, NgSwitchWhen, NgSwitchDefault]);
diff --git a/modules/angular2/docs/core/01_templates.md b/modules/angular2/docs/core/01_templates.md
index ebc63a0bd4f3..864d0ca4c85a 100644
--- a/modules/angular2/docs/core/01_templates.md
+++ b/modules/angular2/docs/core/01_templates.md
@@ -94,7 +94,7 @@ Example:
```
-
+
```
diff --git a/modules/angular2/http.ts b/modules/angular2/http.ts
deleted file mode 100644
index 3e293410cad6..000000000000
--- a/modules/angular2/http.ts
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @module
- * @description
- * The http module provides services to perform http requests. To get started, see the {@link Http}
- * class.
- */
-import {bind, Binding} from 'angular2/di';
-import {Http, Jsonp} from 'angular2/src/http/http';
-import {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend';
-import {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend';
-import {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
-import {BrowserJsonp} from 'angular2/src/http/backends/browser_jsonp';
-import {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
-import {ConnectionBackend} from 'angular2/src/http/interfaces';
-import {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
-
-export {MockConnection, MockBackend} from 'angular2/src/http/backends/mock_backend';
-export {Request} from 'angular2/src/http/static_request';
-export {Response} from 'angular2/src/http/static_response';
-
-export {
- IRequestOptions,
- IResponseOptions,
- Connection,
- ConnectionBackend
-} from 'angular2/src/http/interfaces';
-
-export {BrowserXhr} from 'angular2/src/http/backends/browser_xhr';
-export {BaseRequestOptions, RequestOptions} from 'angular2/src/http/base_request_options';
-export {BaseResponseOptions, ResponseOptions} from 'angular2/src/http/base_response_options';
-export {XHRBackend, XHRConnection} from 'angular2/src/http/backends/xhr_backend';
-export {JSONPBackend, JSONPConnection} from 'angular2/src/http/backends/jsonp_backend';
-export {Http, Jsonp} from 'angular2/src/http/http';
-
-export {Headers} from 'angular2/src/http/headers';
-
-export {
- ResponseTypes,
- ReadyStates,
- RequestMethods,
- RequestCredentialsOpts,
- RequestCacheOpts,
- RequestModesOpts
-} from 'angular2/src/http/enums';
-export {URLSearchParams} from 'angular2/src/http/url_search_params';
-
-/**
- * Provides a basic set of injectables to use the {@link Http} service in any application.
- *
- * #Example
- *
- * ```
- * import {httpInjectables, Http} from 'angular2/http';
- * @Component({selector: 'http-app', viewBindings: [httpInjectables]})
- * @View({template: '{{data}}'})
- * class MyApp {
- * constructor(http:Http) {
- * http.request('data.txt').subscribe(res => this.data = res.text());
- * }
- * }
- * ```
- *
- */
-export var httpInjectables: List
= [
- bind(ConnectionBackend)
- .toClass(XHRBackend),
- BrowserXhr,
- bind(RequestOptions).toClass(BaseRequestOptions),
- bind(ResponseOptions).toClass(BaseResponseOptions),
- Http
-];
-
-export var jsonpInjectables: List = [
- bind(ConnectionBackend)
- .toClass(JSONPBackend),
- BrowserJsonp,
- bind(RequestOptions).toClass(BaseRequestOptions),
- bind(ResponseOptions).toClass(BaseResponseOptions),
- Jsonp
-];
diff --git a/modules/angular2/pipes.ts b/modules/angular2/pipes.ts
index 256ce582fd47..5bf87b165beb 100644
--- a/modules/angular2/pipes.ts
+++ b/modules/angular2/pipes.ts
@@ -4,11 +4,11 @@
* This module provides advanced support for extending change detection.
*/
-export {PromisePipe} from './src/change_detection/pipes/promise_pipe';
-export {UpperCasePipe} from './src/change_detection/pipes/uppercase_pipe';
-export {LowerCasePipe} from './src/change_detection/pipes/lowercase_pipe';
-export {ObservablePipe} from './src/change_detection/pipes/observable_pipe';
-export {JsonPipe} from './src/change_detection/pipes/json_pipe';
-export {DatePipe} from './src/change_detection/pipes/date_pipe';
-export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/change_detection/pipes/number_pipe';
-export {LimitToPipe} from './src/change_detection/pipes/limit_to_pipe';
+export {UpperCasePipe} from './src/pipes/uppercase_pipe';
+export {LowerCasePipe} from './src/pipes/lowercase_pipe';
+export {AsyncPipe} from './src/pipes/async_pipe';
+export {JsonPipe} from './src/pipes/json_pipe';
+export {DatePipe} from './src/pipes/date_pipe';
+export {DecimalPipe, PercentPipe, CurrencyPipe} from './src/pipes/number_pipe';
+export {LimitToPipe} from './src/pipes/limit_to_pipe';
+export {DEFAULT_PIPES_TOKEN, DEFAULT_PIPES} from './src/pipes/default_pipes';
diff --git a/modules/angular2/pubspec.yaml b/modules/angular2/pubspec.yaml
index 6fb8b6c9674d..568a544c1b7d 100644
--- a/modules/angular2/pubspec.yaml
+++ b/modules/angular2/pubspec.yaml
@@ -12,7 +12,7 @@ dependencies:
analyzer: '>=0.24.4 <0.26.0'
barback: '^0.15.2+2'
code_transformers: '^0.2.8'
- dart_style: '^0.1.8'
+ dart_style: '>=0.1.8 <0.3.0'
html: '^0.12.0'
intl: '^0.12.4'
logging: '>=0.9.0 <0.12.0'
diff --git a/modules/angular2/router.ts b/modules/angular2/router.ts
index 33c29c510e29..5dc032fc7ecf 100644
--- a/modules/angular2/router.ts
+++ b/modules/angular2/router.ts
@@ -19,7 +19,9 @@ export * from './src/router/route_config_decorator';
export * from './src/router/route_definition';
export {OnActivate, OnDeactivate, OnReuse, CanDeactivate, CanReuse} from './src/router/interfaces';
export {CanActivate} from './src/router/lifecycle_annotations';
-export {Instruction} from './src/router/instruction';
+export {Instruction, ComponentInstruction} from './src/router/instruction';
+export {Url} from './src/router/url_parser';
+export {OpaqueToken, Type} from 'angular2/angular2';
import {LocationStrategy} from './src/router/location_strategy';
import {HTML5LocationStrategy} from './src/router/html5_location_strategy';
diff --git a/modules/angular2/src/change_detection/abstract_change_detector.ts b/modules/angular2/src/change_detection/abstract_change_detector.ts
index 4fdf665cce85..77e27586d056 100644
--- a/modules/angular2/src/change_detection/abstract_change_detector.ts
+++ b/modules/angular2/src/change_detection/abstract_change_detector.ts
@@ -4,6 +4,7 @@ import {ChangeDetectionUtil} from './change_detection_util';
import {ChangeDetectorRef} from './change_detector_ref';
import {DirectiveRecord} from './directive_record';
import {ChangeDetector, ChangeDispatcher} from './interfaces';
+import {Pipes} from './pipes';
import {
ChangeDetectionError,
ExpressionChangedAfterItHasBeenCheckedException,
@@ -12,7 +13,6 @@ import {
import {ProtoRecord} from './proto_record';
import {BindingRecord} from './binding_record';
import {Locals} from './parser/locals';
-import {Pipes} from './pipes/pipes';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
diff --git a/modules/angular2/src/change_detection/change_detection.ts b/modules/angular2/src/change_detection/change_detection.ts
index caa512cdf7c3..b2846ca573b6 100644
--- a/modules/angular2/src/change_detection/change_detection.ts
+++ b/modules/angular2/src/change_detection/change_detection.ts
@@ -1,23 +1,12 @@
import {JitProtoChangeDetector} from './jit_proto_change_detector';
import {PregenProtoChangeDetector} from './pregen_proto_change_detector';
import {DynamicProtoChangeDetector} from './proto_change_detector';
-import {PipeFactory, Pipe} from './pipes/pipe';
-import {Pipes} from './pipes/pipes';
import {IterableDiffers, IterableDifferFactory} from './differs/iterable_differs';
import {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
import {KeyValueDiffers, KeyValueDifferFactory} from './differs/keyvalue_differs';
import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
-import {ObservablePipeFactory} from './pipes/observable_pipe';
-import {PromisePipeFactory} from './pipes/promise_pipe';
-import {UpperCasePipe} from './pipes/uppercase_pipe';
-import {LowerCasePipe} from './pipes/lowercase_pipe';
-import {JsonPipe} from './pipes/json_pipe';
-import {LimitToPipeFactory} from './pipes/limit_to_pipe';
-import {DatePipe} from './pipes/date_pipe';
-import {DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
-import {NullPipeFactory} from './pipes/null_pipe';
import {ChangeDetection, ProtoChangeDetector, ChangeDetectorDefinition} from './interfaces';
-import {Inject, Injectable, OpaqueToken, Optional} from 'angular2/di';
+import {Injector, Inject, Injectable, OpaqueToken, Optional, Binding} from 'angular2/di';
import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
import {CONST, CONST_EXPR, isPresent, BaseException} from 'angular2/src/facade/lang';
@@ -53,12 +42,10 @@ export {BindingRecord} from './binding_record';
export {DirectiveIndex, DirectiveRecord} from './directive_record';
export {DynamicChangeDetector} from './dynamic_change_detector';
export {ChangeDetectorRef} from './change_detector_ref';
-export {Pipes} from './pipes/pipes';
export {IterableDiffers, IterableDiffer, IterableDifferFactory} from './differs/iterable_differs';
export {KeyValueDiffers, KeyValueDiffer, KeyValueDifferFactory} from './differs/keyvalue_differs';
-export {WrappedValue, Pipe, PipeFactory, BasePipe} from './pipes/pipe';
-export {NullPipe, NullPipeFactory} from './pipes/null_pipe';
-
+export {PipeTransform, BasePipeTransform} from './pipe_transform';
+export {WrappedValue} from './change_detection_util';
/**
* Structural diffing for `Object`s and `Map`s.
@@ -72,76 +59,6 @@ export const keyValDiff: KeyValueDifferFactory[] =
export const iterableDiff: IterableDifferFactory[] =
CONST_EXPR([CONST_EXPR(new DefaultIterableDifferFactory())]);
-/**
- * Async binding to such types as Observable.
- */
-export const async: List = CONST_EXPR([
- CONST_EXPR(new ObservablePipeFactory()),
- CONST_EXPR(new PromisePipeFactory()),
- CONST_EXPR(new NullPipeFactory())
-]);
-
-/**
- * Uppercase text transform.
- */
-export const uppercase: List =
- CONST_EXPR([CONST_EXPR(new UpperCasePipe()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * Lowercase text transform.
- */
-export const lowercase: List =
- CONST_EXPR([CONST_EXPR(new LowerCasePipe()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * Json stringify transform.
- */
-export const json: List =
- CONST_EXPR([CONST_EXPR(new JsonPipe()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * LimitTo text transform.
- */
-export const limitTo: List =
- CONST_EXPR([CONST_EXPR(new LimitToPipeFactory()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * Number number transform.
- */
-export const decimal: List =
- CONST_EXPR([CONST_EXPR(new DecimalPipe()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * Percent number transform.
- */
-export const percent: List =
- CONST_EXPR([CONST_EXPR(new PercentPipe()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * Currency number transform.
- */
-export const currency: List =
- CONST_EXPR([CONST_EXPR(new CurrencyPipe()), CONST_EXPR(new NullPipeFactory())]);
-
-/**
- * Date/time formatter.
- */
-export const date: List =
- CONST_EXPR([CONST_EXPR(new DatePipe()), CONST_EXPR(new NullPipeFactory())]);
-
-
-export const defaultPipes: Pipes = CONST_EXPR(new Pipes({
- "async": async,
- "uppercase": uppercase,
- "lowercase": lowercase,
- "json": json,
- "limitTo": limitTo,
- "number": decimal,
- "percent": percent,
- "currency": currency,
- "date": date
-}));
-
export const defaultIterableDiffers = CONST_EXPR(new IterableDiffers(iterableDiff));
export const defaultKeyValueDiffers = CONST_EXPR(new KeyValueDiffers(keyValDiff));
diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/change_detection/change_detection_jit_generator.ts
index 6eb3914684f0..d2ce2b8d1ec3 100644
--- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts
+++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts
@@ -179,15 +179,10 @@ export class ChangeDetectorJITGenerator {
var newValue = this._names.getLocalName(r.selfIndex);
var pipe = this._names.getPipeName(r.selfIndex);
- var cdRef = "this.ref";
var pipeType = r.name;
-
var read = `
if (${pipe} === ${UTIL}.uninitialized) {
- ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
- } else if (!${pipe}.supports(${context})) {
- ${pipe}.onDestroy();
- ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}', ${context}, ${cdRef});
+ ${pipe} = ${this._names.getPipesAccessorName()}.get('${pipeType}');
}
${newValue} = ${pipe}.transform(${context}, [${argString}]);
`;
diff --git a/modules/angular2/src/change_detection/change_detection_util.ts b/modules/angular2/src/change_detection/change_detection_util.ts
index d924ecea9df0..9b38195cf743 100644
--- a/modules/angular2/src/change_detection/change_detection_util.ts
+++ b/modules/angular2/src/change_detection/change_detection_util.ts
@@ -1,9 +1,47 @@
import {CONST_EXPR, isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang';
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
import {ProtoRecord} from './proto_record';
-import {WrappedValue} from './pipes/pipe';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
+
+/**
+ * Indicates that the result of a {@link Pipe} transformation has changed even though the reference
+ * has not changed.
+ *
+ * The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
+ *
+ * Example:
+ *
+ * ```
+ * if (this._latestValue === this._latestReturnedValue) {
+ * return this._latestReturnedValue;
+ * } else {
+ * this._latestReturnedValue = this._latestValue;
+ * return WrappedValue.wrap(this._latestValue); // this will force update
+ * }
+ * ```
+ */
+export class WrappedValue {
+ constructor(public wrapped: any) {}
+
+ static wrap(value: any): WrappedValue {
+ var w = _wrappedValues[_wrappedIndex++ % 5];
+ w.wrapped = value;
+ return w;
+ }
+}
+
+var _wrappedValues = [
+ new WrappedValue(null),
+ new WrappedValue(null),
+ new WrappedValue(null),
+ new WrappedValue(null),
+ new WrappedValue(null)
+];
+
+var _wrappedIndex = 0;
+
+
export class SimpleChange {
constructor(public previousValue: any, public currentValue: any) {}
diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.ts b/modules/angular2/src/change_detection/dynamic_change_detector.ts
index 21eaa4b14161..6aa0d14a9f6d 100644
--- a/modules/angular2/src/change_detection/dynamic_change_detector.ts
+++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts
@@ -267,13 +267,9 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
_pipeFor(proto: ProtoRecord, context) {
var storedPipe = this._readPipe(proto);
- if (isPresent(storedPipe) && storedPipe.supports(context)) {
- return storedPipe;
- }
- if (isPresent(storedPipe)) {
- storedPipe.onDestroy();
- }
- var pipe = this.pipes.get(proto.name, context, this.ref);
+ if (isPresent(storedPipe)) return storedPipe;
+
+ var pipe = this.pipes.get(proto.name);
this._writePipe(proto, pipe);
return pipe;
}
diff --git a/modules/angular2/src/change_detection/interfaces.ts b/modules/angular2/src/change_detection/interfaces.ts
index 6b49661846e1..5068d4609be0 100644
--- a/modules/angular2/src/change_detection/interfaces.ts
+++ b/modules/angular2/src/change_detection/interfaces.ts
@@ -3,6 +3,7 @@ import {CONST} from 'angular2/src/facade/lang';
import {Locals} from './parser/locals';
import {BindingRecord} from './binding_record';
import {DirectiveIndex, DirectiveRecord} from './directive_record';
+import {ChangeDetectorRef} from './change_detector_ref';
/**
* Interface used by Angular to control the change detection strategy for an application.
@@ -50,6 +51,7 @@ export interface ChangeDispatcher {
export interface ChangeDetector {
parent: ChangeDetector;
mode: string;
+ ref: ChangeDetectorRef;
addChild(cd: ChangeDetector): void;
addShadowDomChild(cd: ChangeDetector): void;
diff --git a/modules/angular2/src/change_detection/pipe_transform.ts b/modules/angular2/src/change_detection/pipe_transform.ts
new file mode 100644
index 000000000000..6e88a1f1b6e4
--- /dev/null
+++ b/modules/angular2/src/change_detection/pipe_transform.ts
@@ -0,0 +1,45 @@
+import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
+
+/**
+ * An interface which all pipes must implement.
+ *
+ * #Example
+ *
+ * ```
+ * class DoublePipe implements PipeTransform {
+ * onDestroy() {}
+ *
+ * transform(value, args = []) {
+ * return `${value}${value}`;
+ * }
+ * }
+ * ```
+ */
+export interface PipeTransform {
+ onDestroy(): void;
+
+ transform(value: any, args: List): any;
+}
+
+/**
+ * Provides default implementation of the `onDestroy` method.
+ *
+ * #Example
+ *
+ * ```
+ * class DoublePipe extends BasePipe {
+ * transform(value) {
+ * return `${value}${value}`;
+ * }
+ * }
+ * ```
+ */
+@CONST()
+export class BasePipeTransform implements PipeTransform {
+ onDestroy(): void {}
+ transform(value: any, args: List): any { return _abstract(); }
+}
+
+function _abstract() {
+ throw new BaseException('This method is abstract');
+}
diff --git a/modules/angular2/src/change_detection/pipes.ts b/modules/angular2/src/change_detection/pipes.ts
new file mode 100644
index 000000000000..b9eb15ee0c4e
--- /dev/null
+++ b/modules/angular2/src/change_detection/pipes.ts
@@ -0,0 +1,3 @@
+import {PipeTransform} from './pipe_transform';
+
+export interface Pipes { get(name: string): PipeTransform; }
\ No newline at end of file
diff --git a/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts b/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts
deleted file mode 100644
index 84f2ae82d445..000000000000
--- a/modules/angular2/src/change_detection/pipes/lowercase_pipe.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import {isString, StringWrapper, CONST} from 'angular2/src/facade/lang';
-import {Pipe, BasePipe, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
-
-/**
- * Implements lowercase transforms to text.
- *
- * # Example
- *
- * In this example we transform the user text lowercase.
- *
- * ```
- * @Component({
- * selector: "username-cmp"
- * })
- * @View({
- * template: "Username: {{ user | lowercase }}"
- * })
- * class Username {
- * user:string;
- * }
- *
- * ```
- */
-@CONST()
-export class LowerCasePipe extends BasePipe implements PipeFactory {
- supports(str: any): boolean { return isString(str); }
-
- transform(value: string, args: List = null): string {
- return StringWrapper.toLowerCase(value);
- }
-
- create(cdRef: ChangeDetectorRef): Pipe { return this; }
-}
diff --git a/modules/angular2/src/change_detection/pipes/null_pipe.ts b/modules/angular2/src/change_detection/pipes/null_pipe.ts
deleted file mode 100644
index 93492424ad57..000000000000
--- a/modules/angular2/src/change_detection/pipes/null_pipe.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import {isBlank, CONST} from 'angular2/src/facade/lang';
-import {Pipe, BasePipe, WrappedValue, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
-
-@CONST()
-export class NullPipeFactory implements PipeFactory {
- supports(obj: any): boolean { return NullPipe.supportsObj(obj); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return new NullPipe(); }
-}
-
-export class NullPipe extends BasePipe {
- called: boolean = false;
-
- static supportsObj(obj: any): boolean { return isBlank(obj); }
-
- supports(obj: any): boolean { return NullPipe.supportsObj(obj); }
-
- transform(value: any, args: List = null): WrappedValue {
- if (!this.called) {
- this.called = true;
- return WrappedValue.wrap(null);
- } else {
- return null;
- }
- }
-}
diff --git a/modules/angular2/src/change_detection/pipes/observable_pipe.ts b/modules/angular2/src/change_detection/pipes/observable_pipe.ts
deleted file mode 100644
index 7abfc5deee49..000000000000
--- a/modules/angular2/src/change_detection/pipes/observable_pipe.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import {Observable, ObservableWrapper} from 'angular2/src/facade/async';
-import {isBlank, isPresent, CONST} from 'angular2/src/facade/lang';
-import {Pipe, WrappedValue, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
-
-/**
- * Implements async bindings to Observable.
- *
- * # Example
- *
- * In this example we bind the description observable to the DOM. The async pipe will convert an
- *observable to the
- * latest value it emitted. It will also request a change detection check when a new value is
- *emitted.
- *
- * ```
- * @Component({
- * selector: "task-cmp",
- * changeDetection: ON_PUSH
- * })
- * @View({
- * template: "Task Description {{ description | async }}"
- * })
- * class Task {
- * description:Observable;
- * }
- *
- * ```
- */
-export class ObservablePipe implements Pipe {
- _latestValue: Object = null;
- _latestReturnedValue: Object = null;
-
- _subscription: Object = null;
- _observable: Observable = null;
-
- constructor(public _ref: ChangeDetectorRef) {}
-
- supports(obs: any): boolean { return ObservableWrapper.isObservable(obs); }
-
- onDestroy(): void {
- if (isPresent(this._subscription)) {
- this._dispose();
- }
- }
-
- transform(obs: Observable, args: List = null): any {
- if (isBlank(this._subscription)) {
- this._subscribe(obs);
- return null;
- }
-
- if (obs !== this._observable) {
- this._dispose();
- return this.transform(obs);
- }
-
- if (this._latestValue === this._latestReturnedValue) {
- return this._latestReturnedValue;
- } else {
- this._latestReturnedValue = this._latestValue;
- return WrappedValue.wrap(this._latestValue);
- }
- }
-
- _subscribe(obs: Observable): void {
- this._observable = obs;
- this._subscription = ObservableWrapper.subscribe(obs, value => this._updateLatestValue(value),
- e => { throw e; });
- }
-
- _dispose(): void {
- ObservableWrapper.dispose(this._subscription);
- this._latestValue = null;
- this._latestReturnedValue = null;
- this._subscription = null;
- this._observable = null;
- }
-
- _updateLatestValue(value: Object) {
- this._latestValue = value;
- this._ref.requestCheck();
- }
-}
-
-/**
- * Provides a factory for [ObervablePipe].
- */
-@CONST()
-export class ObservablePipeFactory implements PipeFactory {
- supports(obs: any): boolean { return ObservableWrapper.isObservable(obs); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return new ObservablePipe(cdRef); }
-}
diff --git a/modules/angular2/src/change_detection/pipes/pipe.ts b/modules/angular2/src/change_detection/pipes/pipe.ts
deleted file mode 100644
index e4f747343d01..000000000000
--- a/modules/angular2/src/change_detection/pipes/pipe.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import {ABSTRACT, BaseException, CONST} from 'angular2/src/facade/lang';
-import {ChangeDetectorRef} from '../change_detector_ref';
-
-/**
- * Indicates that the result of a {@link Pipe} transformation has changed even though the reference
- * has not changed.
- *
- * The wrapped value will be unwrapped by change detection, and the unwrapped value will be stored.
- */
-export class WrappedValue {
- constructor(public wrapped: any) {}
-
- static wrap(value: any): WrappedValue {
- var w = _wrappedValues[_wrappedIndex++ % 5];
- w.wrapped = value;
- return w;
- }
-}
-
-var _wrappedValues = [
- new WrappedValue(null),
- new WrappedValue(null),
- new WrappedValue(null),
- new WrappedValue(null),
- new WrappedValue(null)
-];
-
-var _wrappedIndex = 0;
-
-/**
- * An interface which all pipes must implement.
- *
- * #Example
- *
- * ```
- * class DoublePipe implements Pipe {
- * supports(obj) {
- * return true;
- * }
- *
- * onDestroy() {}
- *
- * transform(value, args = []) {
- * return `${value}${value}`;
- * }
- * }
- * ```
- */
-export interface Pipe {
- /**
- * Query if a pipe supports a particular object instance.
- */
- supports(obj): boolean;
-
- onDestroy(): void;
-
- transform(value: any, args: List): any;
-}
-
-/**
- * Provides default implementation of `supports` and `onDestroy` method.
- *
- * #Example
- *
- * ```
- * class DoublePipe extends BasePipe {
- * transform(value) {
- * return `${value}${value}`;
- * }
- * }
- * ```
- */
-@CONST()
-export class BasePipe implements Pipe {
- supports(obj: any): boolean { return true; }
- onDestroy(): void {}
- transform(value: any, args: List): any { return _abstract(); }
-}
-
-/**
- *
- */
-export interface PipeFactory {
- supports(obs): boolean;
- create(cdRef: ChangeDetectorRef): Pipe;
-}
-
-function _abstract() {
- throw new BaseException('This method is abstract');
-}
diff --git a/modules/angular2/src/change_detection/pipes/pipes.ts b/modules/angular2/src/change_detection/pipes/pipes.ts
deleted file mode 100644
index 7e3a6e51e900..000000000000
--- a/modules/angular2/src/change_detection/pipes/pipes.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import {ListWrapper, isListLikeIterable, StringMapWrapper} from 'angular2/src/facade/collection';
-import {isBlank, isPresent, BaseException, CONST} from 'angular2/src/facade/lang';
-import {Pipe, PipeFactory} from './pipe';
-import {Injectable, OptionalMetadata, SkipSelfMetadata} from 'angular2/di';
-import {ChangeDetectorRef} from '../change_detector_ref';
-import {Binding} from 'angular2/di';
-
-@Injectable()
-@CONST()
-export class Pipes {
- /**
- * Map of {@link Pipe} names to {@link PipeFactory} lists used to configure the
- * {@link Pipes} registry.
- *
- * #Example
- *
- * ```
- * var pipesConfig = {
- * 'json': [jsonPipeFactory]
- * }
- * @Component({
- * viewBindings: [
- * bind(Pipes).toValue(new Pipes(pipesConfig))
- * ]
- * })
- * ```
- */
- config: StringMap;
-
-
- constructor(config: StringMap) { this.config = config; }
-
- get(type: string, obj: any, cdRef?: ChangeDetectorRef, existingPipe?: Pipe): Pipe {
- if (isPresent(existingPipe) && existingPipe.supports(obj)) return existingPipe;
-
- if (isPresent(existingPipe)) existingPipe.onDestroy();
-
- var factories = this._getListOfFactories(type, obj);
- var factory = this._getMatchingFactory(factories, type, obj);
-
- return factory.create(cdRef);
- }
-
- /**
- * Takes a {@link Pipes} config object and returns a binding used to extend the
- * inherited {@link Pipes} instance with the provided config and return a new
- * {@link Pipes} instance.
- *
- * If the provided config contains a key that is not yet present in the
- * inherited {@link Pipes}' config, a new {@link PipeFactory} list will be created
- * for that key. Otherwise, the provided config will be merged with the inherited
- * {@link Pipes} instance by prepending pipes to their respective keys, without mutating
- * the inherited {@link Pipes}.
- *
- * The following example shows how to extend an existing list of `async` factories
- * with a new {@link PipeFactory}, which will only be applied to the injector
- * for this component and its children. This step is all that's required to make a new
- * pipe available to this component's template.
- *
- * # Example
- *
- * ```
- * @Component({
- * viewBindings: [
- * Pipes.extend({
- * async: [newAsyncPipe]
- * })
- * ]
- * })
- * ```
- */
- static extend(config: StringMap): Binding {
- return new Binding(Pipes, {
- toFactory: (pipes: Pipes) => {
- if (isBlank(pipes)) {
- // Typically would occur when calling Pipe.extend inside of dependencies passed to
- // bootstrap(), which would override default pipes instead of extending them.
- throw new BaseException('Cannot extend Pipes without a parent injector');
- }
- return Pipes.create(config, pipes);
- },
- // Dependency technically isn't optional, but we can provide a better error message this way.
- deps: [[Pipes, new SkipSelfMetadata(), new OptionalMetadata()]]
- });
- }
-
- static create(config: StringMap, pipes: Pipes = null): Pipes {
- if (isPresent(pipes)) {
- StringMapWrapper.forEach(pipes.config, (v: PipeFactory[], k: string) => {
- if (StringMapWrapper.contains(config, k)) {
- var configFactories: PipeFactory[] = config[k];
- config[k] = configFactories.concat(v);
- } else {
- config[k] = ListWrapper.clone(v);
- }
- });
- }
- return new Pipes(config);
- }
-
- private _getListOfFactories(type: string, obj: any): PipeFactory[] {
- var listOfFactories = this.config[type];
- if (isBlank(listOfFactories)) {
- throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`);
- }
- return listOfFactories;
- }
-
- private _getMatchingFactory(listOfFactories: PipeFactory[], type: string, obj: any): PipeFactory {
- var matchingFactory =
- ListWrapper.find(listOfFactories, pipeFactory => pipeFactory.supports(obj));
- if (isBlank(matchingFactory)) {
- throw new BaseException(`Cannot find '${type}' pipe supporting object '${obj}'`);
- }
- return matchingFactory;
- }
-}
diff --git a/modules/angular2/src/change_detection/pipes/promise_pipe.ts b/modules/angular2/src/change_detection/pipes/promise_pipe.ts
deleted file mode 100644
index 4b8b00a8bc02..000000000000
--- a/modules/angular2/src/change_detection/pipes/promise_pipe.ts
+++ /dev/null
@@ -1,84 +0,0 @@
-import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
-import {isBlank, isPresent, isPromise, CONST} from 'angular2/src/facade/lang';
-import {Pipe, PipeFactory, WrappedValue} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
-
-/**
- * Implements async bindings to Promise.
- *
- * # Example
- *
- * In this example we bind the description promise to the DOM.
- * The async pipe will convert a promise to the value with which it is resolved. It will also
- * request a change detection check when the promise is resolved.
- *
- * ```
- * @Component({
- * selector: "task-cmp",
- * changeDetection: ON_PUSH
- * })
- * @View({
- * template: "Task Description {{ description | async }}"
- * })
- * class Task {
- * description:Promise;
- * }
- *
- * ```
- */
-export class PromisePipe implements Pipe {
- _latestValue: Object = null;
- _latestReturnedValue: Object = null;
- _sourcePromise: Promise;
-
- constructor(public _ref: ChangeDetectorRef) {}
-
- supports(promise: any): boolean { return isPromise(promise); }
-
- onDestroy(): void {
- if (isPresent(this._sourcePromise)) {
- this._latestValue = null;
- this._latestReturnedValue = null;
- this._sourcePromise = null;
- }
- }
-
- transform(promise: Promise, args: List = null): any {
- if (isBlank(this._sourcePromise)) {
- this._sourcePromise = promise;
- promise.then((val) => {
- if (this._sourcePromise === promise) {
- this._updateLatestValue(val);
- }
- });
- return null;
- }
-
- if (promise !== this._sourcePromise) {
- this._sourcePromise = null;
- return this.transform(promise);
- }
-
- if (this._latestValue === this._latestReturnedValue) {
- return this._latestReturnedValue;
- } else {
- this._latestReturnedValue = this._latestValue;
- return WrappedValue.wrap(this._latestValue);
- }
- }
-
- _updateLatestValue(value: Object) {
- this._latestValue = value;
- this._ref.requestCheck();
- }
-}
-
-/**
- * Provides a factory for [PromisePipe].
- */
-@CONST()
-export class PromisePipeFactory implements PipeFactory {
- supports(promise: any): boolean { return isPromise(promise); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return new PromisePipe(cdRef); }
-}
diff --git a/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts b/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts
deleted file mode 100644
index 033ba7c838c8..000000000000
--- a/modules/angular2/src/change_detection/pipes/uppercase_pipe.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import {isString, StringWrapper, CONST} from 'angular2/src/facade/lang';
-import {Pipe, BasePipe, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
-
-/**
- * Implements uppercase transforms to text.
- *
- * # Example
- *
- * In this example we transform the user text uppercase.
- *
- * ```
- * @Component({
- * selector: "username-cmp"
- * })
- * @View({
- * template: "Username: {{ user | uppercase }}"
- * })
- * class Username {
- * user:string;
- * }
- *
- * ```
- */
-@CONST()
-export class UpperCasePipe extends BasePipe implements PipeFactory {
- supports(str: any): boolean { return isString(str); }
-
- transform(value: string, args: List = null): string {
- return StringWrapper.toUpperCase(value);
- }
-
- create(cdRef: ChangeDetectorRef): Pipe { return this; }
-}
diff --git a/modules/angular2/src/change_detection/pregen_proto_change_detector.dart b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart
index 14469b80e82c..bdb9a7546dc9 100644
--- a/modules/angular2/src/change_detection/pregen_proto_change_detector.dart
+++ b/modules/angular2/src/change_detection/pregen_proto_change_detector.dart
@@ -16,7 +16,7 @@ export 'package:angular2/src/change_detection/directive_record.dart'
show DirectiveIndex, DirectiveRecord;
export 'package:angular2/src/change_detection/interfaces.dart'
show ChangeDetector, ChangeDetectorDefinition, ProtoChangeDetector;
-export 'package:angular2/src/change_detection/pipes/pipes.dart' show Pipes;
+export 'package:angular2/src/change_detection/pipes.dart' show Pipes;
export 'package:angular2/src/change_detection/proto_record.dart'
show ProtoRecord;
export 'package:angular2/src/change_detection/change_detection_util.dart'
diff --git a/modules/angular2/src/core/annotations/annotations.ts b/modules/angular2/src/core/annotations/annotations.ts
index 97833ac6d00c..8e11e0eb543b 100644
--- a/modules/angular2/src/core/annotations/annotations.ts
+++ b/modules/angular2/src/core/annotations/annotations.ts
@@ -6,5 +6,6 @@
export {
Component as ComponentAnnotation,
Directive as DirectiveAnnotation,
+ Pipe as PipeAnnotation,
LifecycleEvent
} from '../annotations_impl/annotations';
diff --git a/modules/angular2/src/core/annotations/decorators.ts b/modules/angular2/src/core/annotations/decorators.ts
index ccc294d61346..c1c40a89dc16 100644
--- a/modules/angular2/src/core/annotations/decorators.ts
+++ b/modules/angular2/src/core/annotations/decorators.ts
@@ -1,4 +1,9 @@
-import {ComponentAnnotation, DirectiveAnnotation, LifecycleEvent} from './annotations';
+import {
+ ComponentAnnotation,
+ DirectiveAnnotation,
+ PipeAnnotation,
+ LifecycleEvent
+} from './annotations';
import {ViewAnnotation} from './view';
import {AttributeAnnotation, QueryAnnotation, ViewQueryAnnotation} from './di';
import {makeDecorator, makeParamDecorator, TypeDecorator, Class} from '../../util/decorators';
@@ -25,6 +30,7 @@ export interface ComponentDecorator extends TypeDecorator {
templateUrl?: string,
template?: string,
directives?: List>,
+ pipes?: List>,
renderer?: string,
styles?: List,
styleUrls?: List,
@@ -44,6 +50,7 @@ export interface ViewDecorator extends TypeDecorator {
templateUrl?: string,
template?: string,
directives?: List>,
+ pipes?: List>,
renderer?: string,
styles?: List,
styleUrls?: List,
@@ -337,6 +344,30 @@ export interface QueryFactory {
new (selector: Type | string, {descendants}?: {descendants?: boolean}): QueryAnnotation;
}
+/**
+ * {@link Pipe} factory for creating decorators.
+ *
+ * ## Example as TypeScript Decorator
+ *
+ * ```
+ * import {Pipe} from "angular2/angular2";
+ *
+ * @Pipe({...})
+ * class MyPipe {
+ * constructor() {
+ * ...
+ * }
+ *
+ * transform(v, args) {}
+ * }
+ * ```
+ */
+export interface PipeFactory {
+ (obj: {name: string}): any;
+ new (obj: {
+ name: string,
+ }): any;
+}
/**
* {@link Component} factory function.
@@ -369,3 +400,8 @@ export var Query: QueryFactory = makeParamDecorator(QueryAnnotation);
* {@link ViewQuery} factory function.
*/
export var ViewQuery: QueryFactory = makeParamDecorator(ViewQueryAnnotation);
+
+/**
+ * {@link Pipe} factory function.
+ */
+export var Pipe: PipeFactory = makeDecorator(PipeAnnotation);
diff --git a/modules/angular2/src/core/annotations_impl/annotations.ts b/modules/angular2/src/core/annotations_impl/annotations.ts
index 8a1c59b8cd42..111624f7d305 100644
--- a/modules/angular2/src/core/annotations_impl/annotations.ts
+++ b/modules/angular2/src/core/annotations_impl/annotations.ts
@@ -923,7 +923,6 @@ export enum LifecycleEvent {
* }
* }
* ```
- * @exportedAs angular2/annotations
*/
onDestroy,
@@ -961,7 +960,6 @@ export enum LifecycleEvent {
* }
* }
* ```
- * @exportedAs angular2/annotations
*/
onChange,
@@ -973,7 +971,7 @@ export enum LifecycleEvent {
*
* It is invoked every time even when none of the directive's bindings has changed.
*
- * ## Example:
+ * ## Example
*
* ```
* @Directive({
@@ -985,7 +983,6 @@ export enum LifecycleEvent {
* }
* }
* ```
- * @exportedAs angular2/annotations
*/
onCheck,
@@ -997,7 +994,7 @@ export enum LifecycleEvent {
*
* It is invoked only once.
*
- * ## Example:
+ * ## Example
*
* ```
* @Directive({
@@ -1009,7 +1006,6 @@ export enum LifecycleEvent {
* }
* }
* ```
- * @exportedAs angular2/annotations
*/
onInit,
@@ -1017,7 +1013,7 @@ export enum LifecycleEvent {
* Notify a directive when the bindings of all its children have been checked (whether they have
* changed or not).
*
- * ## Example:
+ * ## Example
*
* ```
* @Directive({
@@ -1031,7 +1027,30 @@ export enum LifecycleEvent {
*
* }
* ```
- * @exportedAs angular2/annotations
*/
onAllChangesDone
}
+
+/**
+ * Declare reusable pipe function.
+ *
+ * ## Example
+ *
+ * ```
+ * @Pipe({
+ * name: 'lowercase'
+ * })
+ * class Lowercase {
+ * transform(v, args) { return v.toLowerCase(); }
+ * }
+ * ```
+ */
+@CONST()
+export class Pipe extends InjectableMetadata {
+ name: string;
+
+ constructor({name}: {name: string}) {
+ super();
+ this.name = name;
+ }
+}
diff --git a/modules/angular2/src/core/annotations_impl/view.ts b/modules/angular2/src/core/annotations_impl/view.ts
index cbaf1cd9eb17..eb83dfd0ea67 100644
--- a/modules/angular2/src/core/annotations_impl/view.ts
+++ b/modules/angular2/src/core/annotations_impl/view.ts
@@ -87,6 +87,8 @@ export class View {
// for an unused import.
directives: List>;
+ pipes: List>;
+
/**
* Specify how the template and the styles should be encapsulated.
* The default is {@link ViewEncapsulation#EMULATED `ViewEncapsulation.EMULATED`} if the view
@@ -95,10 +97,11 @@ export class View {
*/
encapsulation: ViewEncapsulation;
- constructor({templateUrl, template, directives, encapsulation, styles, styleUrls}: {
+ constructor({templateUrl, template, directives, pipes, encapsulation, styles, styleUrls}: {
templateUrl?: string,
template?: string,
directives?: List>,
+ pipes?: List>,
encapsulation?: ViewEncapsulation,
styles?: List,
styleUrls?: List,
@@ -108,6 +111,7 @@ export class View {
this.styleUrls = styleUrls;
this.styles = styles;
this.directives = directives;
+ this.pipes = pipes;
this.encapsulation = encapsulation;
}
}
diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts
index f3a9bcf0d0e6..13c9b441f579 100644
--- a/modules/angular2/src/core/application_common.ts
+++ b/modules/angular2/src/core/application_common.ts
@@ -21,19 +21,19 @@ import {
DynamicChangeDetection,
JitChangeDetection,
PreGeneratedChangeDetection,
- Pipes,
- defaultPipes,
IterableDiffers,
defaultIterableDiffers,
KeyValueDiffers,
defaultKeyValueDiffers
} from 'angular2/src/change_detection/change_detection';
+import {DEFAULT_PIPES} from 'angular2/pipes';
import {ExceptionHandler} from './exception_handler';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
import {ViewResolver} from './compiler/view_resolver';
import {DirectiveResolver} from './compiler/directive_resolver';
+import {PipeResolver} from './compiler/pipe_resolver';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {Promise, PromiseWrapper, PromiseCompleter} from 'angular2/src/facade/async';
import {NgZone} from 'angular2/src/core/zone/ng_zone';
@@ -137,12 +137,13 @@ function _injectorBindings(appComponentType): List> {
Compiler,
CompilerCache,
ViewResolver,
- bind(Pipes).toValue(defaultPipes),
+ DEFAULT_PIPES,
bind(IterableDiffers).toValue(defaultIterableDiffers),
bind(KeyValueDiffers).toValue(defaultKeyValueDiffers),
bind(ChangeDetection).toClass(bestChangeDetection),
ViewLoader,
DirectiveResolver,
+ PipeResolver,
Parser,
Lexer,
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM, isDart ? false : true), []),
diff --git a/modules/angular2/src/core/compiler/compiler.ts b/modules/angular2/src/core/compiler/compiler.ts
index ef5b0d60748a..45e13164e6b2 100644
--- a/modules/angular2/src/core/compiler/compiler.ts
+++ b/modules/angular2/src/core/compiler/compiler.ts
@@ -1,4 +1,4 @@
-import {Binding, resolveForwardRef, Injectable} from 'angular2/di';
+import {Binding, resolveForwardRef, Injectable, Inject} from 'angular2/di';
import {
Type,
isBlank,
@@ -19,6 +19,7 @@ import {AppProtoView, AppProtoViewMergeMapping} from './view';
import {ProtoViewRef} from './view_ref';
import {DirectiveBinding} from './element_injector';
import {ViewResolver} from './view_resolver';
+import {PipeResolver} from './pipe_resolver';
import {View} from '../annotations_impl/view';
import {ComponentUrlMapper} from './component_url_mapper';
import {ProtoViewFactory} from './proto_view_factory';
@@ -26,6 +27,8 @@ import {UrlResolver} from 'angular2/src/services/url_resolver';
import {AppRootUrl} from 'angular2/src/services/app_root_url';
import {ElementBinder} from './element_binder';
import {wtfStartTimeRange, wtfEndTimeRange} from '../../profile/profile';
+import {PipeBinding} from '../pipes/pipe_binding';
+import {DEFAULT_PIPES_TOKEN} from 'angular2/pipes';
import * as renderApi from 'angular2/src/render/api';
@@ -83,46 +86,40 @@ export class CompilerCache {
*/
@Injectable()
export class Compiler {
- private _reader: DirectiveResolver;
- private _compilerCache: CompilerCache;
- private _compiling: Map>;
- private _viewResolver: ViewResolver;
- private _componentUrlMapper: ComponentUrlMapper;
- private _urlResolver: UrlResolver;
+ private _compiling: Map> = new Map();
private _appUrl: string;
- private _render: renderApi.RenderCompiler;
- private _protoViewFactory: ProtoViewFactory;
+ private _defaultPipes: Type[];
/**
* @private
*/
- constructor(reader: DirectiveResolver, cache: CompilerCache, viewResolver: ViewResolver,
- componentUrlMapper: ComponentUrlMapper, urlResolver: UrlResolver,
- render: renderApi.RenderCompiler, protoViewFactory: ProtoViewFactory,
- appUrl: AppRootUrl) {
- this._reader = reader;
- this._compilerCache = cache;
- this._compiling = new Map();
- this._viewResolver = viewResolver;
- this._componentUrlMapper = componentUrlMapper;
- this._urlResolver = urlResolver;
+ constructor(private _directiveResolver: DirectiveResolver, private _pipeResolver: PipeResolver,
+ @Inject(DEFAULT_PIPES_TOKEN) _defaultPipes: Type[],
+ private _compilerCache: CompilerCache, private _viewResolver: ViewResolver,
+ private _componentUrlMapper: ComponentUrlMapper, private _urlResolver: UrlResolver,
+ private _render: renderApi.RenderCompiler,
+ private _protoViewFactory: ProtoViewFactory, appUrl: AppRootUrl) {
+ this._defaultPipes = _defaultPipes;
this._appUrl = appUrl.value;
- this._render = render;
- this._protoViewFactory = protoViewFactory;
}
private _bindDirective(directiveTypeOrBinding): DirectiveBinding {
if (directiveTypeOrBinding instanceof DirectiveBinding) {
return directiveTypeOrBinding;
} else if (directiveTypeOrBinding instanceof Binding) {
- let annotation = this._reader.resolve(directiveTypeOrBinding.token);
+ let annotation = this._directiveResolver.resolve(directiveTypeOrBinding.token);
return DirectiveBinding.createFromBinding(directiveTypeOrBinding, annotation);
} else {
- let annotation = this._reader.resolve(directiveTypeOrBinding);
+ let annotation = this._directiveResolver.resolve(directiveTypeOrBinding);
return DirectiveBinding.createFromType(directiveTypeOrBinding, annotation);
}
}
+ private _bindPipe(typeOrBinding): PipeBinding {
+ let meta = this._pipeResolver.resolve(typeOrBinding);
+ return PipeBinding.createFromType(typeOrBinding, meta);
+ }
+
// Create a hostView as if the compiler encountered .
// Used for bootstrapping.
compileInHost(componentTypeOrBinding: Type | Binding): Promise {
@@ -143,7 +140,7 @@ export class Compiler {
this._render.compileHost(directiveMetadata)
.then((hostRenderPv) => {
var protoViews = this._protoViewFactory.createAppProtoViews(
- componentBinding, hostRenderPv, [componentBinding]);
+ componentBinding, hostRenderPv, [componentBinding], []);
return this._compileNestedProtoViews(protoViews, componentType, new Map());
})
.then((appProtoView) => {
@@ -186,14 +183,17 @@ export class Compiler {
}
var boundDirectives = this._removeDuplicatedDirectives(
- ListWrapper.map(directives, (directive) => this._bindDirective(directive)));
+ directives.map(directive => this._bindDirective(directive)));
+
+ var pipes = this._flattenPipes(view);
+ var boundPipes = pipes.map(pipe => this._bindPipe(pipe));
var renderTemplate = this._buildRenderTemplate(component, view, boundDirectives);
resultPromise =
this._render.compile(renderTemplate)
.then((renderPv) => {
var protoViews = this._protoViewFactory.createAppProtoViews(
- componentBinding, renderPv, boundDirectives);
+ componentBinding, renderPv, boundDirectives, boundPipes);
return this._compileNestedProtoViews(protoViews, component, componentPath);
})
.then((appProtoView) => {
@@ -317,12 +317,17 @@ export class Compiler {
});
}
- private _flattenDirectives(template: View): List {
- if (isBlank(template.directives)) return [];
+ private _flattenPipes(view: View): any[] {
+ if (isBlank(view.pipes)) return this._defaultPipes;
+ var pipes = ListWrapper.clone(this._defaultPipes);
+ this._flattenList(view.pipes, pipes);
+ return pipes;
+ }
+ private _flattenDirectives(view: View): List {
+ if (isBlank(view.directives)) return [];
var directives = [];
- this._flattenList(template.directives, directives);
-
+ this._flattenList(view.directives, directives);
return directives;
}
diff --git a/modules/angular2/src/core/compiler/element_injector.ts b/modules/angular2/src/core/compiler/element_injector.ts
index adde9c88b1b5..289e245f1c7e 100644
--- a/modules/angular2/src/core/compiler/element_injector.ts
+++ b/modules/angular2/src/core/compiler/element_injector.ts
@@ -42,14 +42,11 @@ import {ElementRef} from './element_ref';
import {TemplateRef} from './template_ref';
import {Directive, Component, LifecycleEvent} from 'angular2/src/core/annotations_impl/annotations';
import {hasLifecycleHook} from './directive_lifecycle_reflector';
-import {
- ChangeDetector,
- ChangeDetectorRef,
- Pipes
-} from 'angular2/src/change_detection/change_detection';
+import {ChangeDetector, ChangeDetectorRef} from 'angular2/src/change_detection/change_detection';
import {QueryList} from './query_list';
import {reflector} from 'angular2/src/reflection/reflection';
import {DirectiveMetadata} from 'angular2/src/render/api';
+import {PipeBinding} from '../pipes/pipe_binding';
var _staticKeys;
@@ -59,7 +56,6 @@ export class StaticKeys {
viewContainerId: number;
changeDetectorRefId: number;
elementRefId: number;
- pipesKey: Key;
constructor() {
this.viewManagerId = Key.get(avmModule.AppViewManager).id;
@@ -67,8 +63,6 @@ export class StaticKeys {
this.viewContainerId = Key.get(ViewContainerRef).id;
this.changeDetectorRefId = Key.get(ChangeDetectorRef).id;
this.elementRefId = Key.get(ElementRef).id;
- // not an id because the public API of injector works only with keys and tokens
- this.pipesKey = Key.get(Pipes);
}
static instance(): StaticKeys {
@@ -427,7 +421,7 @@ class _Context {
export class ElementInjector extends TreeNode implements DependencyProvider {
private _host: ElementInjector;
- private _preBuiltObjects = null;
+ private _preBuiltObjects: PreBuiltObjects = null;
// Queries are added during construction or linking with a new parent.
// They are removed only through unlinking.
@@ -483,19 +477,35 @@ export class ElementInjector extends TreeNode implements Depend
this._host = host;
this._preBuiltObjects = preBuiltObjects;
- this._reattachInjectors(imperativelyCreatedInjector);
- this._strategy.hydrate();
-
if (isPresent(host)) {
this._addViewQueries(host);
}
+ this._reattachInjectors(imperativelyCreatedInjector);
+ this._strategy.hydrate();
+
this._addDirectivesToQueries();
this._addVarBindingsToQueries();
+ // TODO(rado): optimize this call, if view queries are not moved around,
+ // simply appending to the query list is faster than updating.
+ this._updateViewQueries();
+
this.hydrated = true;
}
+ private _updateViewQueries() {
+ if (isPresent(this._query0) && this._query0.isViewQuery) {
+ this._query0.update();
+ }
+ if (isPresent(this._query1) && this._query1.isViewQuery) {
+ this._query1.update();
+ }
+ if (isPresent(this._query2) && this._query2.isViewQuery) {
+ this._query2.update();
+ }
+ }
+
private _debugContext(): any {
var p = this._preBuiltObjects;
var index = p.elementRef.boundElementIndex - p.view.elementOffset;
@@ -541,11 +551,6 @@ export class ElementInjector extends TreeNode implements Depend
injector.internalStrategy.attach(parentInjector, isBoundary);
}
- getPipes(): Pipes {
- var pipesKey = StaticKeys.instance().pipesKey;
- return this._injector.getOptional(pipesKey);
- }
-
hasVariableBinding(name: string): boolean {
var vb = this._proto.directiveVariableBindings;
return isPresent(vb) && vb.has(name);
@@ -589,49 +594,55 @@ export class ElementInjector extends TreeNode implements Depend
getDependency(injector: Injector, binding: ResolvedBinding, dep: Dependency): any {
var key: Key = dep.key;
- if (!(dep instanceof DirectiveDependency)) return undefinedValue;
- if (!(binding instanceof DirectiveBinding)) return undefinedValue;
+ if (binding instanceof DirectiveBinding) {
+ var dirDep = dep;
+ var dirBin = binding;
+ var staticKeys = StaticKeys.instance();
- var dirDep = dep;
- var dirBin = binding;
- var staticKeys = StaticKeys.instance();
+ if (key.id === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager;
- if (key.id === staticKeys.viewManagerId) return this._preBuiltObjects.viewManager;
+ if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep);
- if (isPresent(dirDep.attributeName)) return this._buildAttribute(dirDep);
+ if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list;
- if (isPresent(dirDep.queryDecorator)) return this._findQuery(dirDep.queryDecorator).list;
+ if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
+ // We provide the component's view change detector to components and
+ // the surrounding component's change detector to directives.
+ if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
+ var componentView = this._preBuiltObjects.view.getNestedView(
+ this._preBuiltObjects.elementRef.boundElementIndex);
+ return componentView.changeDetector.ref;
+ } else {
+ return this._preBuiltObjects.view.changeDetector.ref;
+ }
+ }
- if (dirDep.key.id === StaticKeys.instance().changeDetectorRefId) {
- // We provide the component's view change detector to components and
- // the surrounding component's change detector to directives.
- if (dirBin.metadata.type === DirectiveMetadata.COMPONENT_TYPE) {
- var componentView = this._preBuiltObjects.view.getNestedView(
- this._preBuiltObjects.elementRef.boundElementIndex);
- return componentView.changeDetector.ref;
- } else {
- return this._preBuiltObjects.view.changeDetector.ref;
+ if (dirDep.key.id === StaticKeys.instance().elementRefId) {
+ return this.getElementRef();
}
- }
- if (dirDep.key.id === StaticKeys.instance().elementRefId) {
- return this.getElementRef();
- }
+ if (dirDep.key.id === StaticKeys.instance().viewContainerId) {
+ return this.getViewContainerRef();
+ }
- if (dirDep.key.id === StaticKeys.instance().viewContainerId) {
- return this.getViewContainerRef();
- }
+ if (dirDep.key.id === StaticKeys.instance().templateRefId) {
+ if (isBlank(this._preBuiltObjects.templateRef)) {
+ if (dirDep.optional) {
+ return null;
+ }
- if (dirDep.key.id === StaticKeys.instance().templateRefId) {
- if (isBlank(this._preBuiltObjects.templateRef)) {
- if (dirDep.optional) {
- return null;
+ throw new NoBindingError(null, dirDep.key);
}
+ return this._preBuiltObjects.templateRef;
+ }
- throw new NoBindingError(null, dirDep.key);
+ } else if (binding instanceof PipeBinding) {
+ if (dep.key.id === StaticKeys.instance().changeDetectorRefId) {
+ var componentView = this._preBuiltObjects.view.getNestedView(
+ this._preBuiltObjects.elementRef.boundElementIndex);
+ return componentView.changeDetector.ref;
}
- return this._preBuiltObjects.templateRef;
}
return undefinedValue;
@@ -656,22 +667,19 @@ export class ElementInjector extends TreeNode implements Depend
}
private _addViewQueries(host: ElementInjector): void {
- if (isPresent(host._query0) && host._query0.originator == host &&
- host._query0.query.isViewQuery)
- this._addViewQuery(host._query0);
- if (isPresent(host._query1) && host._query1.originator == host &&
- host._query1.query.isViewQuery)
- this._addViewQuery(host._query1);
- if (isPresent(host._query2) && host._query2.originator == host &&
- host._query2.query.isViewQuery)
- this._addViewQuery(host._query2);
- }
-
- private _addViewQuery(queryRef: QueryRef): void {
- // TODO(rado): Replace this.parent check with distanceToParent = 1 when
- // https://github.com/angular/angular/issues/2707 is fixed.
- if (!queryRef.query.descendants && isPresent(this.parent)) return;
- this._assignQueryRef(queryRef);
+ this._addViewQuery(host._query0, host);
+ this._addViewQuery(host._query1, host);
+ this._addViewQuery(host._query2, host);
+ }
+
+ private _addViewQuery(queryRef: QueryRef, host: ElementInjector): void {
+ if (isBlank(queryRef) || !queryRef.isViewQuery || this._hasQuery(queryRef)) return;
+ if (host._query0.originator == host) {
+ // TODO(rado): Replace this.parent check with distanceToParent = 1 when
+ // https://github.com/angular/angular/issues/2707 is fixed.
+ if (!queryRef.query.descendants && isPresent(this.parent)) return;
+ this._assignQueryRef(queryRef);
+ }
}
private _addVarBindingsToQueries(): void {
@@ -699,6 +707,7 @@ export class ElementInjector extends TreeNode implements Depend
private _addDirectivesToQuery(queryRef: QueryRef): void {
if (isBlank(queryRef) || queryRef.query.isVarBindingQuery) return;
+ if (queryRef.isViewQuery && queryRef.originator == this) return;
var matched = [];
this.addDirectivesMatchingQuery(queryRef.query, matched);
@@ -719,6 +728,10 @@ export class ElementInjector extends TreeNode implements Depend
}
addDirectivesMatchingQuery(query: Query, list: any[]): void {
+ var templateRef = this._preBuiltObjects.templateRef;
+ if (query.selector === TemplateRef && isPresent(templateRef)) {
+ list.push(templateRef);
+ }
this._strategy.addDirectivesMatchingQuery(query, list);
}
@@ -755,42 +768,37 @@ export class ElementInjector extends TreeNode implements Depend
this._addParentQueries();
}
+ unlink(): void {
+ var parent = this.parent;
+ this.remove();
+ this._removeParentQueries(parent);
+ }
+
private _addParentQueries(): void {
if (isBlank(this.parent)) return;
- if (isPresent(this.parent._query0) && !this.parent._query0.query.isViewQuery) {
- this._addQueryToTree(this.parent._query0);
- if (this.hydrated) this.parent._query0.update();
- }
- if (isPresent(this.parent._query1) && !this.parent._query1.query.isViewQuery) {
- this._addQueryToTree(this.parent._query1);
- if (this.hydrated) this.parent._query1.update();
- }
- if (isPresent(this.parent._query2) && !this.parent._query2.query.isViewQuery) {
- this._addQueryToTree(this.parent._query2);
- if (this.hydrated) this.parent._query2.update();
- }
+ this._addParentQuery(this.parent._query0);
+ this._addParentQuery(this.parent._query1);
+ this._addParentQuery(this.parent._query2);
}
- unlink(): void {
- var queriesToUpdate = [];
- if (isPresent(this.parent._query0)) {
- this._pruneQueryFromTree(this.parent._query0);
- queriesToUpdate.push(this.parent._query0);
- }
- if (isPresent(this.parent._query1)) {
- this._pruneQueryFromTree(this.parent._query1);
- queriesToUpdate.push(this.parent._query1);
- }
- if (isPresent(this.parent._query2)) {
- this._pruneQueryFromTree(this.parent._query2);
- queriesToUpdate.push(this.parent._query2);
+ private _addParentQuery(query): void {
+ if (isPresent(query) && !this._hasQuery(query)) {
+ this._addQueryToTree(query);
+ if (this.hydrated) query.update();
}
+ }
- this.remove();
- // TODO(rado): update should work on view queries too, however currently it
- // is not implemented, so we filter to non-view queries.
- var nonViewQueries = ListWrapper.filter(queriesToUpdate, (q) => !q.query.isViewQuery);
- ListWrapper.forEach(nonViewQueries, (q) => q.update());
+ private _removeParentQueries(parent: ElementInjector): void {
+ this._removeParentQuery(parent._query0);
+ this._removeParentQuery(parent._query1);
+ this._removeParentQuery(parent._query2);
+ }
+
+ private _removeParentQuery(query: QueryRef) {
+ if (isPresent(query)) {
+ this._pruneQueryFromTree(query);
+ query.update();
+ }
}
private _pruneQueryFromTree(query: QueryRef): void {
@@ -853,6 +861,12 @@ export class ElementInjector extends TreeNode implements Depend
getHost(): ElementInjector { return this._host; }
getBoundElementIndex(): number { return this._proto.index; }
+
+ getRootViewInjectors(): ElementInjector[] {
+ var view = this._preBuiltObjects.view;
+ return view.getNestedView(view.elementOffset + this.getBoundElementIndex())
+ .rootElementInjectors;
+ }
}
interface _ElementInjectorStrategy {
@@ -1134,9 +1148,19 @@ export class QueryRef {
constructor(public query: Query, public list: QueryList,
public originator: ElementInjector) {}
+ get isViewQuery(): boolean { return this.query.isViewQuery; }
+
update(): void {
var aggregator = [];
- this.visit(this.originator, aggregator);
+ if (this.query.isViewQuery) {
+ // intentionally skipping originator for view queries.
+ var rootViewInjectors = this.originator.getRootViewInjectors();
+ for (var i = 0; i < rootViewInjectors.length; i++) {
+ this.visit(rootViewInjectors[i], aggregator);
+ }
+ } else {
+ this.visit(this.originator, aggregator);
+ }
this.list.reset(aggregator);
}
diff --git a/modules/angular2/src/core/compiler/pipe_resolver.ts b/modules/angular2/src/core/compiler/pipe_resolver.ts
new file mode 100644
index 000000000000..a18923bb644f
--- /dev/null
+++ b/modules/angular2/src/core/compiler/pipe_resolver.ts
@@ -0,0 +1,30 @@
+import {resolveForwardRef, Injectable} from 'angular2/di';
+import {Type, isPresent, BaseException, stringify} from 'angular2/src/facade/lang';
+import {Pipe} from '../annotations_impl/annotations';
+import {reflector} from 'angular2/src/reflection/reflection';
+
+/**
+ * Resolve a `Type` for {@link Pipe}.
+ *
+ * This interface can be overridden by the application developer to create custom behavior.
+ *
+ * See {@link Compiler}
+ */
+@Injectable()
+export class PipeResolver {
+ /**
+ * Return {@link Pipe} for a given `Type`.
+ */
+ resolve(type: Type): Pipe {
+ var metas = reflector.annotations(resolveForwardRef(type));
+ if (isPresent(metas)) {
+ for (var i = 0; i < metas.length; i++) {
+ var annotation = metas[i];
+ if (annotation instanceof Pipe) {
+ return annotation;
+ }
+ }
+ }
+ throw new BaseException(`No Pipe decorator found on ${stringify(type)}`);
+ }
+}
diff --git a/modules/angular2/src/core/compiler/proto_view_factory.ts b/modules/angular2/src/core/compiler/proto_view_factory.ts
index 2f1bb11f9ba5..a510865afe12 100644
--- a/modules/angular2/src/core/compiler/proto_view_factory.ts
+++ b/modules/angular2/src/core/compiler/proto_view_factory.ts
@@ -15,6 +15,9 @@ import {
ASTWithSource
} from 'angular2/src/change_detection/change_detection';
+import {PipeBinding} from 'angular2/src/core/pipes/pipe_binding';
+import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
+
import * as renderApi from 'angular2/src/render/api';
import {AppProtoView} from './view';
import {ElementBinder} from './element_binder';
@@ -160,7 +163,7 @@ export class ProtoViewFactory {
createAppProtoViews(hostComponentBinding: DirectiveBinding,
rootRenderProtoView: renderApi.ProtoViewDto,
- allDirectives: List): AppProtoView[] {
+ allDirectives: List, pipes: PipeBinding[]): AppProtoView[] {
var allRenderDirectiveMetadata =
ListWrapper.map(allDirectives, directiveBinding => directiveBinding.metadata);
var nestedPvsWithIndex = _collectNestedProtoViews(rootRenderProtoView);
@@ -177,7 +180,7 @@ export class ProtoViewFactory {
ListWrapper.forEach(nestedPvsWithIndex, (pvWithIndex: RenderProtoViewWithIndex) => {
var appProtoView =
_createAppProtoView(pvWithIndex.renderProtoView, protoChangeDetectors[pvWithIndex.index],
- nestedPvVariableBindings[pvWithIndex.index], allDirectives);
+ nestedPvVariableBindings[pvWithIndex.index], allDirectives, pipes);
if (isPresent(pvWithIndex.parentIndex)) {
var parentView = appProtoViews[pvWithIndex.parentIndex];
parentView.elementBinders[pvWithIndex.boundElementIndex].nestedProtoView = appProtoView;
@@ -252,14 +255,16 @@ function _getChangeDetectorDefinitions(
function _createAppProtoView(
renderProtoView: renderApi.ProtoViewDto, protoChangeDetector: ProtoChangeDetector,
- variableBindings: Map, allDirectives: List): AppProtoView {
+ variableBindings: Map, allDirectives: List,
+ pipes: PipeBinding[]): AppProtoView {
var elementBinders = renderProtoView.elementBinders;
// Embedded ProtoViews that contain `` will be merged into their parents and use
// a RenderFragmentRef. I.e. renderProtoView.transitiveNgContentCount > 0.
+ var protoPipes = new ProtoPipes(pipes);
var protoView = new AppProtoView(
renderProtoView.type, renderProtoView.transitiveNgContentCount > 0, renderProtoView.render,
protoChangeDetector, variableBindings, createVariableLocations(elementBinders),
- renderProtoView.textBindings.length);
+ renderProtoView.textBindings.length, protoPipes);
_createElementBinders(protoView, elementBinders, allDirectives);
_bindDirectiveEvents(protoView, elementBinders);
@@ -361,10 +366,13 @@ function _createProtoElementInjector(binderIndex, parentPeiWithDistance, renderE
componentDirectiveBinding, directiveBindings) {
var protoElementInjector = null;
// Create a protoElementInjector for any element that either has bindings *or* has one
- // or more var- defined. Elements with a var- defined need a their own element injector
- // so that, when hydrating, $implicit can be set to the element.
+ // or more var- defined *or* for elements:
+ // - Elements with a var- defined need a their own element injector
+ // so that, when hydrating, $implicit can be set to the element.
+ // - elements need their own ElementInjector so that we can query their TemplateRef
var hasVariables = MapWrapper.size(renderElementBinder.variableBindings) > 0;
- if (directiveBindings.length > 0 || hasVariables) {
+ if (directiveBindings.length > 0 || hasVariables ||
+ isPresent(renderElementBinder.nestedProtoView)) {
var directiveVariableBindings =
createDirectiveVariableBindings(renderElementBinder, directiveBindings);
protoElementInjector =
diff --git a/modules/angular2/src/core/compiler/template_ref.ts b/modules/angular2/src/core/compiler/template_ref.ts
index 0491791afb36..e01c601b6398 100644
--- a/modules/angular2/src/core/compiler/template_ref.ts
+++ b/modules/angular2/src/core/compiler/template_ref.ts
@@ -28,5 +28,5 @@ export class TemplateRef {
/**
* Whether this template has a local variable with the given name
*/
- hasLocal(name: string): boolean { return this._getProtoView().protoLocals.has(name); }
+ hasLocal(name: string): boolean { return this._getProtoView().variableBindings.has(name); }
}
diff --git a/modules/angular2/src/core/compiler/view.ts b/modules/angular2/src/core/compiler/view.ts
index 7cfadfcd6e1e..b988029b084f 100644
--- a/modules/angular2/src/core/compiler/view.ts
+++ b/modules/angular2/src/core/compiler/view.ts
@@ -31,6 +31,7 @@ import * as renderApi from 'angular2/src/render/api';
import {RenderEventDispatcher} from 'angular2/src/render/api';
import {ViewRef, ProtoViewRef, internalView} from './view_ref';
import {ElementRef} from './element_ref';
+import {ProtoPipes} from 'angular2/src/core/pipes/pipes';
export {DebugContext} from 'angular2/src/change_detection/interfaces';
@@ -335,7 +336,8 @@ export class AppProtoView {
public render: renderApi.RenderProtoViewRef,
public protoChangeDetector: ProtoChangeDetector,
public variableBindings: Map,
- public variableLocations: Map, public textBindingCount: number) {
+ public variableLocations: Map, public textBindingCount: number,
+ public pipes: ProtoPipes) {
this.ref = new ProtoViewRef(this);
if (isPresent(variableBindings)) {
MapWrapper.forEach(variableBindings,
diff --git a/modules/angular2/src/core/compiler/view_manager_utils.ts b/modules/angular2/src/core/compiler/view_manager_utils.ts
index a88c3f0331ca..7d91fade95ba 100644
--- a/modules/angular2/src/core/compiler/view_manager_utils.ts
+++ b/modules/angular2/src/core/compiler/view_manager_utils.ts
@@ -9,6 +9,7 @@ import {ElementRef} from './element_ref';
import {TemplateRef} from './template_ref';
import {Renderer, RenderViewWithFragments} from 'angular2/src/render/api';
import {Locals} from 'angular2/src/change_detection/change_detection';
+import {Pipes} from 'angular2/src/core/pipes/pipes';
import {RenderViewRef, RenderFragmentRef, ViewType} from 'angular2/src/render/api';
@Injectable()
@@ -119,13 +120,14 @@ export class AppViewManagerUtils {
parentView.viewContainers[boundElementIndex] = viewContainer;
}
ListWrapper.insert(viewContainer.views, atIndex, view);
+ var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
+
var sibling;
if (atIndex == 0) {
- sibling = null;
+ sibling = elementInjector;
} else {
sibling = ListWrapper.last(viewContainer.views[atIndex - 1].rootElementInjectors);
}
- var elementInjector = contextView.elementInjectors[contextBoundElementIndex];
for (var i = view.rootElementInjectors.length - 1; i >= 0; i--) {
if (isPresent(elementInjector.parent)) {
view.rootElementInjectors[i].linkAfter(elementInjector.parent, sibling);
@@ -206,21 +208,15 @@ export class AppViewManagerUtils {
this._setUpHostActions(currView, elementInjector, boundElementIndex);
}
}
- var pipes = this._getPipes(imperativelyCreatedInjector, hostElementInjector);
+ var pipes = isPresent(hostElementInjector) ?
+ new Pipes(currView.proto.pipes, hostElementInjector.getInjector()) :
+ null;
currView.changeDetector.hydrate(currView.context, currView.locals, currView, pipes);
viewIdx++;
}
}
}
- _getPipes(imperativelyCreatedInjector: Injector, hostElementInjector: eli.ElementInjector) {
- var pipesKey = eli.StaticKeys.instance().pipesKey;
- if (isPresent(imperativelyCreatedInjector))
- return imperativelyCreatedInjector.getOptional(pipesKey);
- if (isPresent(hostElementInjector)) return hostElementInjector.getPipes();
- return null;
- }
-
_populateViewLocals(view: viewModule.AppView, elementInjector: eli.ElementInjector,
boundElementIdx: number): void {
if (isPresent(elementInjector.getDirectiveVariableBindings())) {
diff --git a/modules/angular2/src/core/pipes/pipe_binding.ts b/modules/angular2/src/core/pipes/pipe_binding.ts
new file mode 100644
index 000000000000..bd6a1643fb08
--- /dev/null
+++ b/modules/angular2/src/core/pipes/pipe_binding.ts
@@ -0,0 +1,15 @@
+import {Type} from 'angular2/src/facade/lang';
+import {Key, Dependency, ResolvedBinding, Binding} from 'angular2/di';
+import {Pipe} from 'angular2/src/core/annotations_impl/annotations';
+
+export class PipeBinding extends ResolvedBinding {
+ constructor(public name: string, key: Key, factory: Function, dependencies: Dependency[]) {
+ super(key, factory, dependencies);
+ }
+
+ static createFromType(type: Type, metadata: Pipe): PipeBinding {
+ var binding = new Binding(type, {toClass: type});
+ var rb = binding.resolve();
+ return new PipeBinding(metadata.name, rb.key, rb.factory, rb.dependencies);
+ }
+}
\ No newline at end of file
diff --git a/modules/angular2/src/core/pipes/pipes.ts b/modules/angular2/src/core/pipes/pipes.ts
new file mode 100644
index 000000000000..31a639520999
--- /dev/null
+++ b/modules/angular2/src/core/pipes/pipes.ts
@@ -0,0 +1,28 @@
+import {isBlank, isPresent, BaseException, CONST, Type} from 'angular2/src/facade/lang';
+import {Injectable, OptionalMetadata, SkipSelfMetadata, Binding, Injector, bind} from 'angular2/di';
+import {PipeBinding} from './pipe_binding';
+import * as cd from 'angular2/src/change_detection/pipes';
+
+export class ProtoPipes {
+ /**
+ * Map of {@link Pipe} names to {@link Pipe} implementations.
+ */
+ config: StringMap = {};
+
+ constructor(bindings: PipeBinding[]) { bindings.forEach(b => this.config[b.name] = b); }
+
+ get(name: string): PipeBinding {
+ var binding = this.config[name];
+ if (isBlank(binding)) throw new BaseException(`Cannot find pipe '${name}'.`);
+ return binding;
+ }
+}
+
+export class Pipes implements cd.Pipes {
+ constructor(public proto: ProtoPipes, public injector: Injector) {}
+
+ get(name: string): any {
+ var b = this.proto.get(name);
+ return this.injector.instantiateResolved(b);
+ }
+}
diff --git a/modules/angular2/src/core/testability/get_testability.dart b/modules/angular2/src/core/testability/get_testability.dart
index 65eacc71eb0e..450f753da5f7 100644
--- a/modules/angular2/src/core/testability/get_testability.dart
+++ b/modules/angular2/src/core/testability/get_testability.dart
@@ -93,11 +93,14 @@ class GetTestability {
static addToWindow(TestabilityRegistry registry) {
js.context['getAngularTestability'] = _jsify((Element elem) {
Testability testability = registry.findTestabilityInTree(elem);
+ if (testability == null) {
+ throw 'Could not find testability for element.';
+ }
return _jsify(new PublicTestability(testability));
});
js.context['getAllAngularTestabilities'] = _jsify(() {
List testabilities = registry.getAllTestabilities();
- List publicTestabilities = testabilities
+ var publicTestabilities = testabilities
.map((testability) => new PublicTestability(testability));
return _jsify(publicTestabilities);
});
diff --git a/modules/angular2/src/di/binding.ts b/modules/angular2/src/di/binding.ts
index 1156a33e7ad1..a842894ea0cb 100644
--- a/modules/angular2/src/di/binding.ts
+++ b/modules/angular2/src/di/binding.ts
@@ -400,9 +400,7 @@ function _extractToken(typeOrFunc, metadata /*List | any*/, params: List this._strategy.getMaxNumberOfObjects()) {
throw new CyclicDependencyError(this, binding.key);
}
+ return this._instantiate(binding, visibility);
+ }
+ private _instantiate(binding: ResolvedBinding, visibility: number): any {
var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;
diff --git a/modules/angular2/src/directives/class.ts b/modules/angular2/src/directives/ng_class.ts
similarity index 95%
rename from modules/angular2/src/directives/class.ts
rename to modules/angular2/src/directives/ng_class.ts
index 2a9e0ab8655f..b0dbc59a3753 100644
--- a/modules/angular2/src/directives/class.ts
+++ b/modules/angular2/src/directives/ng_class.ts
@@ -25,17 +25,17 @@ import {ListWrapper, StringMapWrapper, isListLikeIterable} from 'angular2/src/fa
* # Example:
*
* ```
- * 0}">
+ *
0}">
* Please check errors.
*
* ```
*/
@Directive({
- selector: '[class]',
+ selector: '[ng-class]',
lifecycle: [LifecycleEvent.onCheck, LifecycleEvent.onDestroy],
- properties: ['rawClass: class']
+ properties: ['rawClass: ng-class']
})
-export class CSSClass {
+export class NgClass {
private _differ: any;
private _mode: string;
_rawClass;
diff --git a/modules/angular2/src/http/url_search_params.ts b/modules/angular2/src/http/url_search_params.ts
deleted file mode 100644
index 596f54f076f3..000000000000
--- a/modules/angular2/src/http/url_search_params.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import {isPresent, isBlank, StringWrapper} from 'angular2/src/facade/lang';
-import {
- Map,
- MapWrapper,
- List,
- ListWrapper,
- isListLikeIterable
-} from 'angular2/src/facade/collection';
-
-function paramParser(rawParams: string): Map
> {
- var map: Map> = new Map();
- var params: List = StringWrapper.split(rawParams, new RegExp('&'));
- ListWrapper.forEach(params, (param: string) => {
- var split: List = StringWrapper.split(param, new RegExp('='));
- var key = ListWrapper.get(split, 0);
- var val = ListWrapper.get(split, 1);
- var list = isPresent(map.get(key)) ? map.get(key) : [];
- list.push(val);
- map.set(key, list);
- });
- return map;
-}
-
-/**
- * Map-like representation of url search parameters, based on
- * [URLSearchParams](https://url.spec.whatwg.org/#urlsearchparams) in the url living standard.
- *
- */
-export class URLSearchParams {
- paramsMap: Map>;
- constructor(public rawParams: string) { this.paramsMap = paramParser(rawParams); }
-
- has(param: string): boolean { return this.paramsMap.has(param); }
-
- get(param: string): string {
- var storedParam = this.paramsMap.get(param);
- if (isListLikeIterable(storedParam)) {
- return ListWrapper.first(storedParam);
- } else {
- return null;
- }
- }
-
- getAll(param: string): List {
- var mapParam = this.paramsMap.get(param);
- return isPresent(mapParam) ? mapParam : [];
- }
-
- append(param: string, val: string): void {
- var mapParam = this.paramsMap.get(param);
- var list = isPresent(mapParam) ? mapParam : [];
- list.push(val);
- this.paramsMap.set(param, list);
- }
-
- toString(): string {
- var paramsList = [];
- MapWrapper.forEach(this.paramsMap, (values, k) => {
- ListWrapper.forEach(values, v => { paramsList.push(k + '=' + v); });
- });
- return ListWrapper.join(paramsList, '&');
- }
-
- delete (param: string): void { MapWrapper.delete(this.paramsMap, param); }
-}
diff --git a/modules/angular2/src/pipes/async_pipe.ts b/modules/angular2/src/pipes/async_pipe.ts
new file mode 100644
index 000000000000..58d43a2d12ab
--- /dev/null
+++ b/modules/angular2/src/pipes/async_pipe.ts
@@ -0,0 +1,130 @@
+import {isBlank, isPresent, isPromise, CONST, BaseException} from 'angular2/src/facade/lang';
+import {Observable, Promise, ObservableWrapper} from 'angular2/src/facade/async';
+import {Injectable} from 'angular2/di';
+
+import {PipeTransform, WrappedValue} from 'angular2/change_detection';
+import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
+import {ChangeDetectorRef} from 'angular2/change_detection';
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
+
+
+class ObservableStrategy {
+ createSubscription(async: any, updateLatestValue: any): any {
+ return ObservableWrapper.subscribe(async, updateLatestValue, e => { throw e; });
+ }
+
+ dispose(subscription: any): void { ObservableWrapper.dispose(subscription); }
+
+ onDestroy(subscription: any): void { ObservableWrapper.dispose(subscription); }
+}
+
+class PromiseStrategy {
+ createSubscription(async: any, updateLatestValue: any): any {
+ return async.then(updateLatestValue);
+ }
+
+ dispose(subscription: any): void {}
+
+ onDestroy(subscription: any): void {}
+}
+
+var _promiseStrategy = new PromiseStrategy();
+var _observableStrategy = new ObservableStrategy();
+
+
+/**
+ * Implements async bindings to Observable and Promise.
+ *
+ * # Example
+ *
+ * In this example we bind the description observable to the DOM. The async pipe will convert an
+ *observable to the
+ * latest value it emitted. It will also request a change detection check when a new value is
+ *emitted.
+ *
+ * ```
+ * @Component({
+ * selector: "task-cmp",
+ * changeDetection: ON_PUSH
+ * })
+ * @View({
+ * template: "Task Description {{ description | async }}"
+ * })
+ * class Task {
+ * description:Observable;
+ * }
+ *
+ * ```
+ */
+@Pipe({name: 'async'})
+@Injectable()
+export class AsyncPipe implements PipeTransform {
+ _latestValue: Object = null;
+ _latestReturnedValue: Object = null;
+
+ _subscription: Object = null;
+ _obj: Observable | Promise = null;
+ private _strategy: any = null;
+
+ constructor(public _ref: ChangeDetectorRef) {}
+
+ onDestroy(): void {
+ if (isPresent(this._subscription)) {
+ this._dispose();
+ }
+ }
+
+ transform(obj: Observable | Promise, args?: any[]): any {
+ if (isBlank(this._obj)) {
+ if (isPresent(obj)) {
+ this._subscribe(obj);
+ }
+ return null;
+ }
+
+ if (obj !== this._obj) {
+ this._dispose();
+ return this.transform(obj);
+ }
+
+ if (this._latestValue === this._latestReturnedValue) {
+ return this._latestReturnedValue;
+ } else {
+ this._latestReturnedValue = this._latestValue;
+ return WrappedValue.wrap(this._latestValue);
+ }
+ }
+
+ _subscribe(obj: Observable | Promise): void {
+ this._obj = obj;
+ this._strategy = this._selectStrategy(obj);
+ this._subscription =
+ this._strategy.createSubscription(obj, value => this._updateLatestValue(obj, value));
+ }
+
+ _selectStrategy(obj: Observable | Promise): any {
+ if (isPromise(obj)) {
+ return _promiseStrategy;
+ } else if (ObservableWrapper.isObservable(obj)) {
+ return _observableStrategy;
+ } else {
+ throw new InvalidPipeArgumentException(AsyncPipe, obj);
+ }
+ }
+
+ _dispose(): void {
+ this._strategy.dispose(this._subscription);
+ this._latestValue = null;
+ this._latestReturnedValue = null;
+ this._subscription = null;
+ this._obj = null;
+ }
+
+ _updateLatestValue(async: any, value: Object) {
+ if (async === this._obj) {
+ this._latestValue = value;
+ this._ref.requestCheck();
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/angular2/src/change_detection/pipes/date_pipe.ts b/modules/angular2/src/pipes/date_pipe.ts
similarity index 87%
rename from modules/angular2/src/change_detection/pipes/date_pipe.ts
rename to modules/angular2/src/pipes/date_pipe.ts
index 5dde144c7094..78867a0d05ca 100644
--- a/modules/angular2/src/change_detection/pipes/date_pipe.ts
+++ b/modules/angular2/src/pipes/date_pipe.ts
@@ -5,12 +5,17 @@ import {
Date,
DateWrapper,
CONST,
+ isBlank,
FunctionWrapper
} from 'angular2/src/facade/lang';
import {DateFormatter} from 'angular2/src/facade/intl';
+import {Injectable} from 'angular2/di';
import {StringMapWrapper, ListWrapper} from 'angular2/src/facade/collection';
-import {Pipe, BasePipe, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
+
+import {PipeTransform, WrappedValue, BasePipeTransform} from 'angular2/change_detection';
+import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
// TODO: move to a global configable location along with other i18n components.
var defaultLocale: string = 'en-US';
@@ -70,7 +75,9 @@ var defaultLocale: string = 'en-US';
* {{ dateObj | date:'mmss' }} // output is '43:11'
*/
@CONST()
-export class DatePipe extends BasePipe implements PipeFactory {
+@Pipe({name: 'date'})
+@Injectable()
+export class DatePipe extends BasePipeTransform {
static _ALIASES = {
'medium': 'yMMMdjms',
'short': 'yMdjm',
@@ -84,6 +91,12 @@ export class DatePipe extends BasePipe implements PipeFactory {
transform(value: any, args: List): 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);
@@ -94,7 +107,5 @@ export class DatePipe extends BasePipe implements PipeFactory {
return DateFormatter.format(value, defaultLocale, pattern);
}
- supports(obj: any): boolean { return isDate(obj) || isNumber(obj); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return this; }
+ private supports(obj: any): boolean { return isDate(obj) || isNumber(obj); }
}
diff --git a/modules/angular2/src/pipes/default_pipes.ts b/modules/angular2/src/pipes/default_pipes.ts
new file mode 100644
index 000000000000..1bc679f4f30f
--- /dev/null
+++ b/modules/angular2/src/pipes/default_pipes.ts
@@ -0,0 +1,27 @@
+import {AsyncPipe} from './async_pipe';
+import {UpperCasePipe} from './uppercase_pipe';
+import {LowerCasePipe} from './lowercase_pipe';
+import {JsonPipe} from './json_pipe';
+import {LimitToPipe} from './limit_to_pipe';
+import {DatePipe} from './date_pipe';
+import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
+
+import {CONST_EXPR} from 'angular2/src/facade/lang';
+import {Binding, OpaqueToken} from 'angular2/di';
+
+const DEFAULT_PIPES_LIST = CONST_EXPR([
+ AsyncPipe,
+ UpperCasePipe,
+ LowerCasePipe,
+ JsonPipe,
+ LimitToPipe,
+ DecimalPipe,
+ PercentPipe,
+ CurrencyPipe,
+ DatePipe
+]);
+
+export const DEFAULT_PIPES_TOKEN = CONST_EXPR(new OpaqueToken("Default Pipes"));
+
+export const DEFAULT_PIPES =
+ CONST_EXPR(new Binding(DEFAULT_PIPES_TOKEN, {toValue: DEFAULT_PIPES_LIST}));
diff --git a/modules/angular2/src/pipes/invalid_pipe_argument_exception.ts b/modules/angular2/src/pipes/invalid_pipe_argument_exception.ts
new file mode 100644
index 000000000000..44601f137302
--- /dev/null
+++ b/modules/angular2/src/pipes/invalid_pipe_argument_exception.ts
@@ -0,0 +1,7 @@
+import {ABSTRACT, BaseException, CONST, Type} from 'angular2/src/facade/lang';
+
+export class InvalidPipeArgumentException extends BaseException {
+ constructor(type: Type, value: Object) {
+ super(`Invalid argument '${value}' for pipe '${type}'`);
+ }
+}
diff --git a/modules/angular2/src/change_detection/pipes/json_pipe.ts b/modules/angular2/src/pipes/json_pipe.ts
similarity index 65%
rename from modules/angular2/src/change_detection/pipes/json_pipe.ts
rename to modules/angular2/src/pipes/json_pipe.ts
index 2b6512ce8c92..a088d95c691f 100644
--- a/modules/angular2/src/change_detection/pipes/json_pipe.ts
+++ b/modules/angular2/src/pipes/json_pipe.ts
@@ -1,6 +1,9 @@
import {isBlank, isPresent, Json, CONST} from 'angular2/src/facade/lang';
-import {Pipe, BasePipe, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
+import {Injectable} from 'angular2/di';
+
+import {PipeTransform, WrappedValue, BasePipeTransform} from 'angular2/change_detection';
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
/**
* Implements json transforms to any object.
@@ -26,8 +29,8 @@ import {ChangeDetectorRef} from '../change_detector_ref';
* ```
*/
@CONST()
-export class JsonPipe extends BasePipe implements PipeFactory {
+@Pipe({name: 'json'})
+@Injectable()
+export class JsonPipe extends BasePipeTransform {
transform(value: any, args: List = null): string { return Json.stringify(value); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return this; }
}
diff --git a/modules/angular2/src/change_detection/pipes/limit_to_pipe.ts b/modules/angular2/src/pipes/limit_to_pipe.ts
similarity index 77%
rename from modules/angular2/src/change_detection/pipes/limit_to_pipe.ts
rename to modules/angular2/src/pipes/limit_to_pipe.ts
index c7e296dc7475..95ee2e4a1618 100644
--- a/modules/angular2/src/change_detection/pipes/limit_to_pipe.ts
+++ b/modules/angular2/src/pipes/limit_to_pipe.ts
@@ -8,8 +8,12 @@ import {
} from 'angular2/src/facade/lang';
import {ListWrapper} from 'angular2/src/facade/collection';
import {Math} from 'angular2/src/facade/math';
-import {WrappedValue, Pipe, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
+import {Injectable} from 'angular2/di';
+
+import {PipeTransform, WrappedValue, BasePipeTransform} from 'angular2/change_detection';
+import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
/**
* Creates a new List or String containing only a prefix/suffix of the
@@ -50,15 +54,19 @@ import {ChangeDetectorRef} from '../change_detector_ref';
* {{ 'abcdefghij' | limitTo: -4 }} // output is 'ghij'
* {{ 'abcdefghij' | limitTo: -100 }} // output is 'abcdefghij'
*/
-export class LimitToPipe implements Pipe {
- static supportsObj(obj: any): boolean { return isString(obj) || isArray(obj); }
-
- supports(obj: any): boolean { return LimitToPipe.supportsObj(obj); }
+@Pipe({name: 'limitTo'})
+@Injectable()
+export class LimitToPipe implements PipeTransform {
+ supports(obj: any): boolean { return isString(obj) || isArray(obj); }
transform(value: any, args: List = null): any {
if (isBlank(args) || args.length == 0) {
throw new BaseException('limitTo pipe requires one argument');
}
+ if (!this.supports(value)) {
+ throw new InvalidPipeArgumentException(LimitToPipe, value);
+ }
+ if (isBlank(value)) return value;
var limit: int = args[0];
var left = 0, right = Math.min(limit, value.length);
if (limit < 0) {
@@ -73,10 +81,3 @@ export class LimitToPipe implements Pipe {
onDestroy(): void {}
}
-
-@CONST()
-export class LimitToPipeFactory implements PipeFactory {
- supports(obj: any): boolean { return LimitToPipe.supportsObj(obj); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return new LimitToPipe(); }
-}
diff --git a/modules/angular2/src/pipes/lowercase_pipe.ts b/modules/angular2/src/pipes/lowercase_pipe.ts
new file mode 100644
index 000000000000..146d71f03c02
--- /dev/null
+++ b/modules/angular2/src/pipes/lowercase_pipe.ts
@@ -0,0 +1,41 @@
+import {isString, StringWrapper, CONST, isBlank} from 'angular2/src/facade/lang';
+import {Injectable} from 'angular2/di';
+
+import {PipeTransform, WrappedValue, BasePipeTransform} from 'angular2/change_detection';
+import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
+
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
+
+/**
+ * Implements lowercase transforms to text.
+ *
+ * # Example
+ *
+ * In this example we transform the user text lowercase.
+ *
+ * ```
+ * @Component({
+ * selector: "username-cmp"
+ * })
+ * @View({
+ * template: "Username: {{ user | lowercase }}"
+ * })
+ * class Username {
+ * user:string;
+ * }
+ *
+ * ```
+ */
+@CONST()
+@Pipe({name: 'lowercase'})
+@Injectable()
+export class LowerCasePipe extends BasePipeTransform {
+ transform(value: string, args: List = null): string {
+ if (isBlank(value)) return value;
+ if (!isString(value)) {
+ throw new InvalidPipeArgumentException(LowerCasePipe, value);
+ }
+ return StringWrapper.toLowerCase(value);
+ }
+}
diff --git a/modules/angular2/src/change_detection/pipes/number_pipe.ts b/modules/angular2/src/pipes/number_pipe.ts
similarity index 87%
rename from modules/angular2/src/change_detection/pipes/number_pipe.ts
rename to modules/angular2/src/pipes/number_pipe.ts
index 1663a70e4a02..d7048fe8f910 100644
--- a/modules/angular2/src/change_detection/pipes/number_pipe.ts
+++ b/modules/angular2/src/pipes/number_pipe.ts
@@ -10,17 +10,26 @@ import {
FunctionWrapper
} from 'angular2/src/facade/lang';
import {NumberFormatter, NumberFormatStyle} from 'angular2/src/facade/intl';
+import {Injectable} from 'angular2/di';
import {ListWrapper} from 'angular2/src/facade/collection';
-import {Pipe, BasePipe, PipeFactory} from './pipe';
-import {ChangeDetectorRef} from '../change_detector_ref';
+
+import {PipeTransform, WrappedValue, BasePipeTransform} from 'angular2/change_detection';
+import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
var defaultLocale: string = 'en-US';
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
@CONST()
-export class NumberPipe extends BasePipe implements PipeFactory {
+@Injectable()
+export class NumberPipe extends BasePipeTransform {
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
currencyAsSymbol: boolean = false): string {
+ if (isBlank(value)) return null;
+ if (!isNumber(value)) {
+ throw new InvalidPipeArgumentException(NumberPipe, value);
+ }
var minInt = 1, minFraction = 0, maxFraction = 3;
if (isPresent(digits)) {
var parts = RegExpWrapper.firstMatch(_re, digits);
@@ -45,10 +54,6 @@ export class NumberPipe extends BasePipe implements PipeFactory {
currencyAsSymbol: currencyAsSymbol
});
}
-
- supports(obj: any): boolean { return isNumber(obj); }
-
- create(cdRef: ChangeDetectorRef): Pipe { return this; }
}
/**
@@ -77,6 +82,8 @@ export class NumberPipe extends BasePipe implements PipeFactory {
* {{ 1 | number: '2.2' }} // output is 01.00
*/
@CONST()
+@Pipe({name: 'number'})
+@Injectable()
export class DecimalPipe extends NumberPipe {
transform(value: any, args: any[]): string {
var digits: string = ListWrapper.first(args);
@@ -94,6 +101,8 @@ export class DecimalPipe extends NumberPipe {
* For more information about `digitInfo` see {@link DecimalPipe}
*/
@CONST()
+@Pipe({name: 'percent'})
+@Injectable()
export class PercentPipe extends NumberPipe {
transform(value: any, args: any[]): string {
var digits: string = ListWrapper.first(args);
@@ -115,6 +124,8 @@ export class PercentPipe extends NumberPipe {
* For more information about `digitInfo` see {@link DecimalPipe}
*/
@CONST()
+@Pipe({name: 'currency'})
+@Injectable()
export class CurrencyPipe extends NumberPipe {
transform(value: any, args: any[]): string {
var currencyCode: string = isPresent(args) && args.length > 0 ? args[0] : 'USD';
diff --git a/modules/angular2/src/pipes/uppercase_pipe.ts b/modules/angular2/src/pipes/uppercase_pipe.ts
new file mode 100644
index 000000000000..a76768a650ac
--- /dev/null
+++ b/modules/angular2/src/pipes/uppercase_pipe.ts
@@ -0,0 +1,40 @@
+import {isString, StringWrapper, CONST, isBlank} from 'angular2/src/facade/lang';
+import {Injectable} from 'angular2/di';
+
+import {PipeTransform, WrappedValue, BasePipeTransform} from 'angular2/change_detection';
+import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
+
+import {Pipe} from 'angular2/src/core/annotations/decorators';
+
+/**
+ * Implements uppercase transforms to text.
+ *
+ * # Example
+ *
+ * In this example we transform the user text uppercase.
+ *
+ * ```
+ * @Component({
+ * selector: "username-cmp"
+ * })
+ * @View({
+ * template: "Username: {{ user | uppercase }}"
+ * })
+ * class Username {
+ * user:string;
+ * }
+ *
+ * ```
+ */
+@CONST()
+@Pipe({name: 'uppercase'})
+@Injectable()
+export class UpperCasePipe extends BasePipeTransform {
+ transform(value: string, args: List = null): string {
+ if (isBlank(value)) return value;
+ if (!isString(value)) {
+ throw new InvalidPipeArgumentException(UpperCasePipe, value);
+ }
+ return StringWrapper.toUpperCase(value);
+ }
+}
diff --git a/modules/angular2/src/render/dom/compiler/compile_element.ts b/modules/angular2/src/render/dom/compiler/compile_element.ts
index 2333a8573475..6a0face0769c 100644
--- a/modules/angular2/src/render/dom/compiler/compile_element.ts
+++ b/modules/angular2/src/render/dom/compiler/compile_element.ts
@@ -19,7 +19,7 @@ export class CompileElement {
// inherited down to children if they don't have an own elementBinder
inheritedElementBinder: ElementBinderBuilder = null;
compileChildren: boolean = true;
- elementDescription: string; // e.g. '' : used to provide context in case of
+ elementDescription: string; // e.g. '
' : used to provide context in case of
// error
constructor(public element, compilationUnit: string = '') {
@@ -50,8 +50,6 @@ export class CompileElement {
return this.inheritedElementBinder;
}
- refreshAttrs() { this._attrs = null; }
-
attrs(): Map
{
if (isBlank(this._attrs)) {
this._attrs = DOM.attributeMap(this.element);
@@ -59,8 +57,6 @@ export class CompileElement {
return this._attrs;
}
- refreshClassList() { this._classList = null; }
-
classList(): List {
if (isBlank(this._classList)) {
this._classList = [];
diff --git a/modules/angular2/src/render/dom/compiler/compiler.ts b/modules/angular2/src/render/dom/compiler/compiler.ts
index ae633f26985d..5ba058951a5c 100644
--- a/modules/angular2/src/render/dom/compiler/compiler.ts
+++ b/modules/angular2/src/render/dom/compiler/compiler.ts
@@ -82,8 +82,8 @@ export class DomCompiler extends RenderCompiler {
var pipeline = new CompilePipeline(this._stepFactory.createSteps(viewDef));
var compiledStyles = pipeline.processStyles(templateAndStyles.styles);
- var compileElements = pipeline.processElements(DOM.createTemplate(templateAndStyles.template),
- protoViewType, viewDef);
+ var compileElements = pipeline.processElements(
+ this._createTemplateElm(templateAndStyles.template), protoViewType, viewDef);
if (viewDef.encapsulation === ViewEncapsulation.NATIVE) {
prependAll(DOM.content(compileElements[0].element),
compiledStyles.map(style => DOM.createStyleElement(style)));
@@ -95,6 +95,17 @@ export class DomCompiler extends RenderCompiler {
compileElements[0].inheritedProtoView.build(this._schemaRegistry, this._templateCloner));
}
+ _createTemplateElm(template: string) {
+ var templateElm = DOM.createTemplate(template);
+ var scriptTags = DOM.querySelectorAll(DOM.templateAwareRoot(templateElm), 'script');
+
+ for (var i = 0; i < scriptTags.length; i++) {
+ DOM.remove(scriptTags[i]);
+ }
+
+ return templateElm;
+ }
+
_normalizeViewEncapsulationIfThereAreNoStyles(viewDef: ViewDefinition): ViewDefinition {
if (viewDef.encapsulation === ViewEncapsulation.EMULATED) {
return new ViewDefinition({
diff --git a/modules/angular2/src/router/helpers.ts b/modules/angular2/src/router/helpers.ts
deleted file mode 100644
index 49db7319bdef..000000000000
--- a/modules/angular2/src/router/helpers.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import {isPresent} from 'angular2/src/facade/lang';
-
-export function parseAndAssignParamString(splitToken: string, paramString: string,
- keyValueMap: StringMap): void {
- var first = paramString[0];
- if (first == '?' || first == ';') {
- paramString = paramString.substring(1);
- }
-
- paramString.split(splitToken)
- .forEach((entry) => {
- var tuple = entry.split('=');
- var key = tuple[0];
- if (!isPresent(keyValueMap[key])) {
- var value = tuple.length > 1 ? tuple[1] : true;
- keyValueMap[key] = value;
- }
- });
-}
diff --git a/modules/angular2/src/router/instruction.ts b/modules/angular2/src/router/instruction.ts
index b8f4beb417be..5baf08866aca 100644
--- a/modules/angular2/src/router/instruction.ts
+++ b/modules/angular2/src/router/instruction.ts
@@ -6,9 +6,11 @@ import {
List,
ListWrapper
} from 'angular2/src/facade/collection';
-import {isPresent, isBlank, normalizeBlank} from 'angular2/src/facade/lang';
+import {isPresent, isBlank, normalizeBlank, Type} from 'angular2/src/facade/lang';
+import {Promise} from 'angular2/src/facade/async';
import {PathRecognizer} from './path_recognizer';
+import {Url} from './url_parser';
export class RouteParams {
constructor(public params: StringMap) {}
@@ -18,34 +20,82 @@ export class RouteParams {
/**
- * An `Instruction` represents the component hierarchy of the application based on a given route
+ * `Instruction` is a tree of `ComponentInstructions`, with all the information needed
+ * to transition each component in the app to a given route, including all auxiliary routes.
+ *
+ * This is a public API.
*/
export class Instruction {
- // "capturedUrl" is the part of the URL captured by this instruction
- // "accumulatedUrl" is the part of the URL captured by this instruction and all children
- accumulatedUrl: string;
- reuse: boolean = false;
- specificity: number;
-
- constructor(public component: any, public capturedUrl: string,
- private _recognizer: PathRecognizer, public child: Instruction = null,
- private _params: StringMap = null) {
- this.accumulatedUrl = capturedUrl;
- this.specificity = _recognizer.specificity;
- if (isPresent(child)) {
- this.child = child;
- this.specificity += child.specificity;
- var childUrl = child.accumulatedUrl;
- if (isPresent(childUrl)) {
- this.accumulatedUrl += childUrl;
- }
- }
+ constructor(public component: ComponentInstruction, public child: Instruction,
+ public auxInstruction: StringMap) {}
+
+ replaceChild(child: Instruction): Instruction {
+ return new Instruction(this.component, child, this.auxInstruction);
+ }
+}
+
+/**
+ * Represents a partially completed instruction during recognition that only has the
+ * primary (non-aux) route instructions matched.
+ *
+ * `PrimaryInstruction` is an internal class used by `RouteRecognizer` while it's
+ * figuring out where to navigate.
+ */
+export class PrimaryInstruction {
+ constructor(public component: ComponentInstruction, public child: PrimaryInstruction,
+ public auxUrls: List) {}
+}
+
+export function stringifyInstruction(instruction: Instruction): string {
+ var params = instruction.component.urlParams.length > 0 ?
+ ('?' + instruction.component.urlParams.join('&')) :
+ '';
+
+ return instruction.component.urlPath + stringifyAux(instruction) +
+ stringifyPrimary(instruction.child) + params;
+}
+
+function stringifyPrimary(instruction: Instruction): string {
+ if (isBlank(instruction)) {
+ return '';
}
+ var params = instruction.component.urlParams.length > 0 ?
+ (';' + instruction.component.urlParams.join(';')) :
+ '';
+ return '/' + instruction.component.urlPath + params + stringifyAux(instruction) +
+ stringifyPrimary(instruction.child);
+}
- params(): StringMap {
- if (isBlank(this._params)) {
- this._params = this._recognizer.parseParams(this.capturedUrl);
- }
- return this._params;
+function stringifyAux(instruction: Instruction): string {
+ var routes = [];
+ StringMapWrapper.forEach(instruction.auxInstruction, (auxInstruction, _) => {
+ routes.push(stringifyPrimary(auxInstruction));
+ });
+ if (routes.length > 0) {
+ return '(' + routes.join('//') + ')';
}
+ return '';
+}
+
+
+/**
+ * A `ComponentInstruction` represents the route state for a single component. An `Instruction` is
+ * composed of a tree of these `ComponentInstruction`s.
+ *
+ * `ComponentInstructions` is a public API. Instances of `ComponentInstruction` are passed
+ * to route lifecycle hooks, like {@link CanActivate}.
+ */
+export class ComponentInstruction {
+ reuse: boolean = false;
+
+ constructor(public urlPath: string, public urlParams: List,
+ private _recognizer: PathRecognizer, public params: StringMap = null) {}
+
+ get componentType() { return this._recognizer.handler.componentType; }
+
+ resolveComponentType(): Promise { return this._recognizer.handler.resolveComponentType(); }
+
+ get specificity() { return this._recognizer.specificity; }
+
+ get terminal() { return this._recognizer.terminal; }
}
diff --git a/modules/angular2/src/router/interfaces.ts b/modules/angular2/src/router/interfaces.ts
index afa04661d948..88edd16d8b6a 100644
--- a/modules/angular2/src/router/interfaces.ts
+++ b/modules/angular2/src/router/interfaces.ts
@@ -1,4 +1,4 @@
-import {Instruction} from './instruction';
+import {ComponentInstruction} from './instruction';
import {global} from 'angular2/src/facade/lang';
// This is here only so that after TS transpilation the file is not empty.
@@ -8,36 +8,132 @@ var __ignore_me = global;
/**
- * Defines route lifecycle method [onActivate]
+ * Defines route lifecycle method [onActivate], which is called by the router at the end of a
+ * successful route navigation.
+ *
+ * For a single component's navigation, only one of either [onActivate] or [onReuse] will be called,
+ * depending on the result of [canReuse].
+ *
+ * If `onActivate` returns a promise, the route change will wait until the promise settles to
+ * instantiate and activate child components.
+ *
+ * ## Example
+ * ```
+ * @Directive({
+ * selector: 'my-cmp'
+ * })
+ * class MyCmp implements OnActivate {
+ * onActivate(next, prev) {
+ * this.log = 'Finished navigating from ' + prev.urlPath + ' to ' + next.urlPath;
+ * }
+ * }
+ * ```
*/
export interface OnActivate {
- onActivate(nextInstruction: Instruction, prevInstruction: Instruction): any;
+ onActivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any;
}
/**
- * Defines route lifecycle method [onReuse]
+ * Defines route lifecycle method [onReuse], which is called by the router at the end of a
+ * successful route navigation when [canReuse] is implemented and returns or resolves to true.
+ *
+ * For a single component's navigation, only one of either [onActivate] or [onReuse] will be called,
+ * depending on the result of [canReuse].
+ *
+ * ## Example
+ * ```
+ * @Directive({
+ * selector: 'my-cmp'
+ * })
+ * class MyCmp implements CanReuse, OnReuse {
+ * canReuse() {
+ * return true;
+ * }
+ *
+ * onReuse(next, prev) {
+ * this.params = next.params;
+ * }
+ * }
+ * ```
*/
export interface OnReuse {
- onReuse(nextInstruction: Instruction, prevInstruction: Instruction): any;
+ onReuse(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any;
}
/**
- * Defines route lifecycle method [onDeactivate]
+ * Defines route lifecycle method [onDeactivate], which is called by the router before destroying
+ * a component as part of a route change.
+ *
+ * If `onDeactivate` returns a promise, the route change will wait until the promise settles.
+ *
+ * ## Example
+ * ```
+ * @Directive({
+ * selector: 'my-cmp'
+ * })
+ * class MyCmp implements CanReuse, OnReuse {
+ * canReuse() {
+ * return true;
+ * }
+ *
+ * onReuse(next, prev) {
+ * this.params = next.params;
+ * }
+ * }
+ * ```
*/
export interface OnDeactivate {
- onDeactivate(nextInstruction: Instruction, prevInstruction: Instruction): any;
+ onDeactivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any;
}
/**
- * Defines route lifecycle method [canReuse]
+ * Defines route lifecycle method [canReuse], which is called by the router to determine whether a
+ * component should be reused across routes, or whether to destroy and instantiate a new component.
+ *
+ * If `canReuse` returns or resolves to `true`, the component instance will be reused.
+ *
+ * If `canReuse` throws or rejects, the navigation will be cancelled.
+ *
+ * ## Example
+ * ```
+ * @Directive({
+ * selector: 'my-cmp'
+ * })
+ * class MyCmp implements CanReuse, OnReuse {
+ * canReuse(next, prev) {
+ * return next.params.id == prev.params.id;
+ * }
+ *
+ * onReuse(next, prev) {
+ * this.id = next.params.id;
+ * }
+ * }
+ * ```
*/
export interface CanReuse {
- canReuse(nextInstruction: Instruction, prevInstruction: Instruction): any;
+ canReuse(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any;
}
/**
- * Defines route lifecycle method [canDeactivate]
+ * Defines route lifecycle method [canDeactivate], which is called by the router to determine
+ * if a component can be removed as part of a navigation.
+ *
+ * If `canDeactivate` returns or resolves to `false`, the navigation is cancelled.
+ *
+ * If `canDeactivate` throws or rejects, the navigation is also cancelled.
+ *
+ * ## Example
+ * ```
+ * @Directive({
+ * selector: 'my-cmp'
+ * })
+ * class MyCmp implements CanDeactivate {
+ * canDeactivate(next, prev) {
+ * return askUserIfTheyAreSureTheyWantToQuit();
+ * }
+ * }
+ * ```
*/
export interface CanDeactivate {
- canDeactivate(nextInstruction: Instruction, prevInstruction: Instruction): any;
+ canDeactivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any;
}
diff --git a/modules/angular2/src/router/lifecycle_annotations.ts b/modules/angular2/src/router/lifecycle_annotations.ts
index 8532530dfb18..bc00745cbc5a 100644
--- a/modules/angular2/src/router/lifecycle_annotations.ts
+++ b/modules/angular2/src/router/lifecycle_annotations.ts
@@ -6,7 +6,7 @@
import {makeDecorator} from 'angular2/src/util/decorators';
import {CanActivate as CanActivateAnnotation} from './lifecycle_annotations_impl';
import {Promise} from 'angular2/src/facade/async';
-import {Instruction} from 'angular2/src/router/instruction';
+import {ComponentInstruction} from 'angular2/src/router/instruction';
export {
canReuse,
@@ -16,6 +16,28 @@ export {
onDeactivate
} from './lifecycle_annotations_impl';
+/**
+ * Defines route lifecycle method [canActivate], which is called by the router to determine
+ * if a component can be instantiated as part of a navigation.
+ *
+ * Note that unlike other lifecycle hooks, this one uses an annotation rather than an interface.
+ * This is because [canActivate] is called before the component is instantiated.
+ *
+ * If `canActivate` returns or resolves to `false`, the navigation is cancelled.
+ *
+ * If `canActivate` throws or rejects, the navigation is also cancelled.
+ *
+ * ## Example
+ * ```
+ * @Directive({
+ * selector: 'control-panel-cmp'
+ * })
+ * @CanActivate(() => checkIfUserIsLoggedIn())
+ * class ControlPanelCmp {
+ * // ...
+ * }
+ * ```
+ */
export var CanActivate:
- (hook: (next: Instruction, prev: Instruction) => Promise| boolean) => ClassDecorator =
- makeDecorator(CanActivateAnnotation);
+ (hook: (next: ComponentInstruction, prev: ComponentInstruction) => Promise| boolean) =>
+ ClassDecorator = makeDecorator(CanActivateAnnotation);
diff --git a/modules/angular2/src/router/path_recognizer.ts b/modules/angular2/src/router/path_recognizer.ts
index 2d9d06abe296..d44380caa941 100644
--- a/modules/angular2/src/router/path_recognizer.ts
+++ b/modules/angular2/src/router/path_recognizer.ts
@@ -7,7 +7,6 @@ import {
isBlank,
BaseException
} from 'angular2/src/facade/lang';
-import {Promise, PromiseWrapper} from 'angular2/src/facade/async';
import {
Map,
MapWrapper,
@@ -17,21 +16,14 @@ import {
ListWrapper
} from 'angular2/src/facade/collection';
import {IMPLEMENTS} from 'angular2/src/facade/lang';
-import {parseAndAssignParamString} from 'angular2/src/router/helpers';
-import {escapeRegex} from './url';
-import {RouteHandler} from './route_handler';
-// TODO(jeffbcross): implement as interface when ts2dart adds support:
-// https://github.com/angular/ts2dart/issues/173
-export class Segment {
- name: string;
- regex: string;
- generate(params: TouchMap): string { return ''; }
-}
+import {RouteHandler} from './route_handler';
+import {Url, RootUrl, serializeParams} from './url_parser';
+import {ComponentInstruction} from './instruction';
-class TouchMap {
- map: StringMap = StringMapWrapper.create();
- keys: StringMap = StringMapWrapper.create();
+export class TouchMap {
+ map: StringMap = {};
+ keys: StringMap = {};
constructor(map: StringMap) {
if (isPresent(map)) {
@@ -63,31 +55,28 @@ function normalizeString(obj: any): string {
}
}
-class ContinuationSegment extends Segment {}
+export interface Segment {
+ name: string;
+ generate(params: TouchMap): string;
+ match(path: string): boolean;
+}
-class StaticSegment extends Segment {
- regex: string;
+class ContinuationSegment implements Segment {
name: string = '';
-
- constructor(public string: string) {
- super();
- this.regex = escapeRegex(string);
-
- // we add this property so that the route matcher still sees
- // this segment as a valid path even if do not use the matrix
- // parameters
- this.regex += '(;[^\/]+)?';
- }
-
- generate(params: TouchMap): string { return this.string; }
+ generate(params: TouchMap): string { return ''; }
+ match(path: string): boolean { return true; }
}
-@IMPLEMENTS(Segment)
-class DynamicSegment {
- regex: string = "([^/]+)";
+class StaticSegment implements Segment {
+ name: string = '';
+ constructor(public path: string) {}
+ match(path: string): boolean { return path == this.path; }
+ generate(params: TouchMap): string { return this.path; }
+}
+class DynamicSegment implements Segment {
constructor(public name: string) {}
-
+ match(path: string): boolean { return true; }
generate(params: TouchMap): string {
if (!StringMapWrapper.contains(params.map, this.name)) {
throw new BaseException(
@@ -98,11 +87,9 @@ class DynamicSegment {
}
-class StarSegment {
- regex: string = "(.+)";
-
+class StarSegment implements Segment {
constructor(public name: string) {}
-
+ match(path: string): boolean { return true; }
generate(params: TouchMap): string { return normalizeString(params.get(this.name)); }
}
@@ -150,7 +137,7 @@ function parsePathString(route: string): StringMap {
throw new BaseException(`Unexpected "..." before the end of the path for "${route}".`);
}
results.push(new ContinuationSegment());
- } else if (segment.length > 0) {
+ } else {
results.push(new StaticSegment(segment));
specificity += 100 * (100 - i);
}
@@ -161,6 +148,23 @@ function parsePathString(route: string): StringMap {
return result;
}
+// this function is used to determine whether a route config path like `/foo/:id` collides with
+// `/foo/:name`
+function pathDslHash(segments: List): string {
+ return segments.map((segment) => {
+ if (segment instanceof StarSegment) {
+ return '*';
+ } else if (segment instanceof ContinuationSegment) {
+ return '...';
+ } else if (segment instanceof DynamicSegment) {
+ return ':';
+ } else if (segment instanceof StaticSegment) {
+ return segment.path;
+ }
+ })
+ .join('/');
+}
+
function splitBySlash(url: string): List {
return url.split('/');
}
@@ -178,125 +182,106 @@ function assertPath(path: string) {
}
}
+export class PathMatch {
+ constructor(public instruction: ComponentInstruction, public remaining: Url,
+ public remainingAux: List) {}
+}
+
// represents something like '/foo/:bar'
export class PathRecognizer {
- segments: List;
- regex: RegExp;
+ private _segments: List;
specificity: number;
terminal: boolean = true;
+ hash: string;
- static matrixRegex: RegExp = RegExpWrapper.create('^(.*\/[^\/]+?)(;[^\/]+)?\/?$');
- static queryRegex: RegExp = RegExpWrapper.create('^(.*\/[^\/]+?)(\\?[^\/]+)?$');
+ // TODO: cache component instruction instances by params and by ParsedUrl instance
- constructor(public path: string, public handler: RouteHandler, public isRoot: boolean = false) {
+ constructor(public path: string, public handler: RouteHandler) {
assertPath(path);
var parsed = parsePathString(path);
- var specificity = parsed['specificity'];
- var segments = parsed['segments'];
- var regexString = '^';
- ListWrapper.forEach(segments, (segment) => {
- if (segment instanceof ContinuationSegment) {
- this.terminal = false;
- } else {
- regexString += '/' + segment.regex;
- }
- });
+ this._segments = parsed['segments'];
+ this.specificity = parsed['specificity'];
+ this.hash = pathDslHash(this._segments);
- if (this.terminal) {
- regexString += '$';
- }
-
- this.regex = RegExpWrapper.create(regexString);
- this.segments = segments;
- this.specificity = specificity;
+ var lastSegment = this._segments[this._segments.length - 1];
+ this.terminal = !(lastSegment instanceof ContinuationSegment);
}
- parseParams(url: string): StringMap {
- // the last segment is always the star one since it's terminal
- var segmentsLimit = this.segments.length - 1;
- var containsStarSegment =
- segmentsLimit >= 0 && this.segments[segmentsLimit] instanceof StarSegment;
-
- var paramsString, useQueryString = this.isRoot && this.terminal;
- if (!containsStarSegment) {
- var matches = RegExpWrapper.firstMatch(
- useQueryString ? PathRecognizer.queryRegex : PathRecognizer.matrixRegex, url);
- if (isPresent(matches)) {
- url = matches[1];
- paramsString = matches[2];
- }
- url = StringWrapper.replaceAll(url, /(;[^\/]+)(?=(\/|$))/g, '');
- }
+ recognize(beginningSegment: Url): PathMatch {
+ var nextSegment = beginningSegment;
+ var currentSegment: Url;
+ var positionalParams = {};
+ var captured = [];
+
+ for (var i = 0; i < this._segments.length; i += 1) {
+ if (isBlank(nextSegment)) {
+ return null;
+ }
+ currentSegment = nextSegment;
- var params = StringMapWrapper.create();
- var urlPart = url;
+ var segment = this._segments[i];
- for (var i = 0; i <= segmentsLimit; i++) {
- var segment = this.segments[i];
if (segment instanceof ContinuationSegment) {
- continue;
+ break;
}
- var match = RegExpWrapper.firstMatch(RegExpWrapper.create('/' + segment.regex), urlPart);
- urlPart = StringWrapper.substring(urlPart, match[0].length);
- if (segment.name.length > 0) {
- params[segment.name] = match[1];
+ captured.push(currentSegment.path);
+
+ // the star segment consumes all of the remaining URL, including matrix params
+ if (segment instanceof StarSegment) {
+ positionalParams[segment.name] = currentSegment.toString();
+ nextSegment = null;
+ break;
}
- }
- if (isPresent(paramsString) && paramsString.length > 0) {
- var expectedStartingValue = useQueryString ? '?' : ';';
- if (paramsString[0] == expectedStartingValue) {
- parseAndAssignParamString(expectedStartingValue, paramsString, params);
+ if (segment instanceof DynamicSegment) {
+ positionalParams[segment.name] = currentSegment.path;
+ } else if (!segment.match(currentSegment.path)) {
+ return null;
}
+
+ nextSegment = currentSegment.child;
+ }
+
+ if (this.terminal && isPresent(nextSegment)) {
+ return null;
}
- return params;
+ var urlPath = captured.join('/');
+
+ // If this is the root component, read query params. Otherwise, read matrix params.
+ var paramsSegment = beginningSegment instanceof RootUrl ? beginningSegment : currentSegment;
+
+
+ var allParams = isPresent(paramsSegment.params) ?
+ StringMapWrapper.merge(paramsSegment.params, positionalParams) :
+ positionalParams;
+ var urlParams = serializeParams(paramsSegment.params);
+
+ var instruction = new ComponentInstruction(urlPath, urlParams, this, allParams);
+
+ return new PathMatch(instruction, nextSegment, currentSegment.auxiliary);
}
- generate(params: StringMap): string {
+
+ generate(params: StringMap): ComponentInstruction {
var paramTokens = new TouchMap(params);
- var applyLeadingSlash = false;
- var useQueryString = this.isRoot && this.terminal;
- var url = '';
- for (var i = 0; i < this.segments.length; i++) {
- let segment = this.segments[i];
- let s = segment.generate(paramTokens);
- applyLeadingSlash = applyLeadingSlash || (segment instanceof ContinuationSegment);
+ var path = [];
- if (s.length > 0) {
- url += (i > 0 ? '/' : '') + s;
+ for (var i = 0; i < this._segments.length; i++) {
+ let segment = this._segments[i];
+ if (!(segment instanceof ContinuationSegment)) {
+ path.push(segment.generate(paramTokens));
}
}
+ var urlPath = path.join('/');
- var unusedParams = paramTokens.getUnused();
- if (!StringMapWrapper.isEmpty(unusedParams)) {
- url += useQueryString ? '?' : ';';
- var paramToken = useQueryString ? '&' : ';';
- var i = 0;
- StringMapWrapper.forEach(unusedParams, (value, key) => {
- if (i++ > 0) {
- url += paramToken;
- }
- url += key;
- if (!isPresent(value) && useQueryString) {
- value = 'true';
- }
- if (isPresent(value)) {
- url += '=' + value;
- }
- });
- }
+ var nonPositionalParams = paramTokens.getUnused();
+ var urlParams = serializeParams(nonPositionalParams);
- if (applyLeadingSlash) {
- url += '/';
- }
-
- return url;
+ return new ComponentInstruction(urlPath, urlParams, this, params);
}
-
- resolveComponentType(): Promise { return this.handler.resolveComponentType(); }
}
diff --git a/modules/angular2/src/router/route_config_decorator.ts b/modules/angular2/src/router/route_config_decorator.ts
index 955664de94d3..ba518e6b7dc8 100644
--- a/modules/angular2/src/router/route_config_decorator.ts
+++ b/modules/angular2/src/router/route_config_decorator.ts
@@ -2,6 +2,6 @@ import {RouteConfig as RouteConfigAnnotation, RouteDefinition} from './route_con
import {makeDecorator} from 'angular2/src/util/decorators';
import {List} from 'angular2/src/facade/collection';
-export {Route, Redirect, AsyncRoute, RouteDefinition} from './route_config_impl';
+export {Route, Redirect, AuxRoute, AsyncRoute, RouteDefinition} from './route_config_impl';
export var RouteConfig: (configs: List) => ClassDecorator =
makeDecorator(RouteConfigAnnotation);
diff --git a/modules/angular2/src/router/route_config_impl.ts b/modules/angular2/src/router/route_config_impl.ts
index 0a4a6cb8c941..f4e06664ffd7 100644
--- a/modules/angular2/src/router/route_config_impl.ts
+++ b/modules/angular2/src/router/route_config_impl.ts
@@ -8,7 +8,7 @@ export {RouteDefinition} from './route_definition';
*
* Supported keys:
* - `path` (required)
- * - `component`, `redirectTo` (requires exactly one of these)
+ * - `component`, `loader`, `redirectTo` (requires exactly one of these)
* - `as` (optional)
*/
@CONST()
@@ -34,6 +34,21 @@ export class Route implements RouteDefinition {
}
}
+@CONST()
+export class AuxRoute implements RouteDefinition {
+ path: string;
+ component: Type;
+ as: string;
+ // added next two properties to work around https://github.com/Microsoft/TypeScript/issues/4107
+ loader: Function = null;
+ redirectTo: string = null;
+ constructor({path, component, as}: {path: string, component: Type, as?: string}) {
+ this.path = path;
+ this.component = component;
+ this.as = as;
+ }
+}
+
@CONST()
export class AsyncRoute implements RouteDefinition {
path: string;
@@ -51,6 +66,8 @@ export class Redirect implements RouteDefinition {
path: string;
redirectTo: string;
as: string = null;
+ // added next property to work around https://github.com/Microsoft/TypeScript/issues/4107
+ loader: Function = null;
constructor({path, redirectTo}: {path: string, redirectTo: string}) {
this.path = path;
this.redirectTo = redirectTo;
diff --git a/modules/angular2/src/router/route_config_nomalizer.ts b/modules/angular2/src/router/route_config_nomalizer.ts
index 1b401e8a9205..f80998764991 100644
--- a/modules/angular2/src/router/route_config_nomalizer.ts
+++ b/modules/angular2/src/router/route_config_nomalizer.ts
@@ -1,4 +1,4 @@
-import {AsyncRoute, Route, Redirect, RouteDefinition} from './route_config_decorator';
+import {AsyncRoute, AuxRoute, Route, Redirect, RouteDefinition} from './route_config_decorator';
import {ComponentDefinition} from './route_definition';
import {Type, BaseException} from 'angular2/src/facade/lang';
@@ -6,13 +6,14 @@ import {Type, BaseException} from 'angular2/src/facade/lang';
* Given a JS Object that represents... returns a corresponding Route, AsyncRoute, or Redirect
*/
export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
- if (config instanceof Route || config instanceof Redirect || config instanceof AsyncRoute) {
+ if (config instanceof Route || config instanceof Redirect || config instanceof AsyncRoute ||
+ config instanceof AuxRoute) {
return config;
}
if ((!config.component) == (!config.redirectTo)) {
throw new BaseException(
- `Route config should contain exactly one 'component', or 'redirectTo' property`);
+ `Route config should contain exactly one "component", "loader", or "redirectTo" property.`);
}
if (config.component) {
if (typeof config.component == 'object') {
@@ -28,7 +29,7 @@ export function normalizeRouteConfig(config: RouteDefinition): RouteDefinition {
{path: config.path, loader: componentDefinitionObject.loader, as: config.as});
} else {
throw new BaseException(
- `Invalid component type '${componentDefinitionObject.type}'. Valid types are "constructor" and "loader".`);
+ `Invalid component type "${componentDefinitionObject.type}". Valid types are "constructor" and "loader".`);
}
}
return new Route(<{
diff --git a/modules/angular2/src/router/route_recognizer.ts b/modules/angular2/src/router/route_recognizer.ts
index aa8657c3fc16..e556a0c38cca 100644
--- a/modules/angular2/src/router/route_recognizer.ts
+++ b/modules/angular2/src/router/route_recognizer.ts
@@ -6,7 +6,8 @@ import {
isPresent,
isType,
isStringMap,
- BaseException
+ BaseException,
+ Type
} from 'angular2/src/facade/lang';
import {
Map,
@@ -17,12 +18,13 @@ import {
StringMapWrapper
} from 'angular2/src/facade/collection';
-import {PathRecognizer} from './path_recognizer';
-import {RouteHandler} from './route_handler';
-import {Route, AsyncRoute, Redirect, RouteDefinition} from './route_config_impl';
+import {PathRecognizer, PathMatch} from './path_recognizer';
+import {Route, AsyncRoute, AuxRoute, Redirect, RouteDefinition} from './route_config_impl';
import {AsyncRouteHandler} from './async_route_handler';
import {SyncRouteHandler} from './sync_route_handler';
-import {parseAndAssignParamString} from 'angular2/src/router/helpers';
+import {Url} from './url_parser';
+import {ComponentInstruction} from './instruction';
+
/**
* `RouteRecognizer` is responsible for recognizing routes for a single component.
@@ -31,30 +33,45 @@ import {parseAndAssignParamString} from 'angular2/src/router/helpers';
*/
export class RouteRecognizer {
names: Map = new Map();
- redirects: Map = new Map();
- matchers: Map = new Map();
- constructor(public isRoot: boolean = false) {}
+ auxRoutes: Map = new Map();
+
+ // TODO: optimize this into a trie
+ matchers: List = [];
+
+ // TODO: optimize this into a trie
+ redirects: List = [];
config(config: RouteDefinition): boolean {
var handler;
+
+ if (config instanceof AuxRoute) {
+ handler = new SyncRouteHandler(config.component);
+ let path = config.path.startsWith('/') ? config.path.substring(1) : config.path;
+ var recognizer = new PathRecognizer(config.path, handler);
+ this.auxRoutes.set(path, recognizer);
+ return recognizer.terminal;
+ }
if (config instanceof Redirect) {
- let path = config.path == '/' ? '' : config.path;
- this.redirects.set(path, config.redirectTo);
+ this.redirects.push(new Redirector(config.path, config.redirectTo));
return true;
- } else if (config instanceof Route) {
+ }
+
+ if (config instanceof Route) {
handler = new SyncRouteHandler(config.component);
} else if (config instanceof AsyncRoute) {
handler = new AsyncRouteHandler(config.loader);
}
- var recognizer = new PathRecognizer(config.path, handler, this.isRoot);
- MapWrapper.forEach(this.matchers, (matcher, _) => {
- if (recognizer.regex.toString() == matcher.regex.toString()) {
+ var recognizer = new PathRecognizer(config.path, handler);
+
+ this.matchers.forEach((matcher) => {
+ if (recognizer.hash == matcher.hash) {
throw new BaseException(
`Configuration '${config.path}' conflicts with existing route '${matcher.path}'`);
}
});
- this.matchers.set(recognizer.regex, recognizer);
+
+ this.matchers.push(recognizer);
if (isPresent(config.as)) {
this.names.set(config.as, recognizer);
}
@@ -66,102 +83,87 @@ export class RouteRecognizer {
* Given a URL, returns a list of `RouteMatch`es, which are partial recognitions for some route.
*
*/
- recognize(url: string): List {
+ recognize(urlParse: Url): List {
var solutions = [];
- if (url.length > 0 && url[url.length - 1] == '/') {
- url = url.substring(0, url.length - 1);
- }
- MapWrapper.forEach(this.redirects, (target, path) => {
- // "/" redirect case
- if (path == '/' || path == '') {
- if (path == url) {
- url = target;
- }
- } else if (url.startsWith(path)) {
- url = target + url.substring(path.length);
- }
- });
+ urlParse = this._redirect(urlParse);
- var queryParams = StringMapWrapper.create();
- var queryString = '';
- var queryIndex = url.indexOf('?');
- if (queryIndex >= 0) {
- queryString = url.substring(queryIndex + 1);
- url = url.substring(0, queryIndex);
- }
- if (this.isRoot && queryString.length > 0) {
- parseAndAssignParamString('&', queryString, queryParams);
- }
+ this.matchers.forEach((pathRecognizer: PathRecognizer) => {
+ var pathMatch = pathRecognizer.recognize(urlParse);
- MapWrapper.forEach(this.matchers, (pathRecognizer, regex) => {
- var match;
- if (isPresent(match = RegExpWrapper.firstMatch(regex, url))) {
- var matchedUrl = '/';
- var unmatchedUrl = '';
- if (url != '/') {
- matchedUrl = match[0];
- unmatchedUrl = url.substring(match[0].length);
- }
- var params = null;
- if (pathRecognizer.terminal && !StringMapWrapper.isEmpty(queryParams)) {
- params = queryParams;
- matchedUrl += '?' + queryString;
- }
- solutions.push(new RouteMatch(pathRecognizer, matchedUrl, unmatchedUrl, params));
+ if (isPresent(pathMatch)) {
+ solutions.push(pathMatch);
}
});
return solutions;
}
+ _redirect(urlParse: Url): Url {
+ for (var i = 0; i < this.redirects.length; i += 1) {
+ let redirector = this.redirects[i];
+ var redirectedUrl = redirector.redirect(urlParse);
+ if (isPresent(redirectedUrl)) {
+ return redirectedUrl;
+ }
+ }
+
+ return urlParse;
+ }
+
+ recognizeAuxiliary(urlParse: Url): PathMatch {
+ var pathRecognizer = this.auxRoutes.get(urlParse.path);
+ if (isBlank(pathRecognizer)) {
+ return null;
+ }
+ return pathRecognizer.recognize(urlParse);
+ }
+
hasRoute(name: string): boolean { return this.names.has(name); }
- generate(name: string, params: any): StringMap {
+ generate(name: string, params: any): ComponentInstruction {
var pathRecognizer: PathRecognizer = this.names.get(name);
if (isBlank(pathRecognizer)) {
return null;
}
- var url = pathRecognizer.generate(params);
- return {url, 'nextComponent': pathRecognizer.handler.componentType};
+ return pathRecognizer.generate(params);
}
}
-export class RouteMatch {
- private _params: StringMap;
- private _paramsParsed: boolean = false;
+export class Redirector {
+ segments: List = [];
+ toSegments: List = [];
- constructor(public recognizer: PathRecognizer, public matchedUrl: string,
- public unmatchedUrl: string, p: StringMap = null) {
- this._params = isPresent(p) ? p : StringMapWrapper.create();
- }
-
- params(): StringMap {
- if (!this._paramsParsed) {
- this._paramsParsed = true;
- StringMapWrapper.forEach(this.recognizer.parseParams(this.matchedUrl),
- (value, key) => { StringMapWrapper.set(this._params, key, value); });
+ constructor(path: string, redirectTo: string) {
+ if (path.startsWith('/')) {
+ path = path.substring(1);
+ }
+ this.segments = path.split('/');
+ if (redirectTo.startsWith('/')) {
+ redirectTo = redirectTo.substring(1);
}
- return this._params;
+ this.toSegments = redirectTo.split('/');
}
-}
-function configObjToHandler(config: any): RouteHandler {
- if (isType(config)) {
- return new SyncRouteHandler(config);
- } else if (isStringMap(config)) {
- if (isBlank(config['type'])) {
- throw new BaseException(
- `Component declaration when provided as a map should include a 'type' property`);
+ /**
+ * Returns `null` or a `ParsedUrl` representing the new path to match
+ */
+ redirect(urlParse: Url): Url {
+ for (var i = 0; i < this.segments.length; i += 1) {
+ if (isBlank(urlParse)) {
+ return null;
+ }
+ let segment = this.segments[i];
+ if (segment != urlParse.path) {
+ return null;
+ }
+ urlParse = urlParse.child;
}
- var componentType = config['type'];
- if (componentType == 'constructor') {
- return new SyncRouteHandler(config['constructor']);
- } else if (componentType == 'loader') {
- return new AsyncRouteHandler(config['loader']);
- } else {
- throw new BaseException(`oops`);
+
+ for (var i = this.toSegments.length - 1; i >= 0; i -= 1) {
+ let segment = this.toSegments[i];
+ urlParse = new Url(segment, urlParse);
}
+ return urlParse;
}
- throw new BaseException(`Unexpected component "${config}".`);
}
diff --git a/modules/angular2/src/router/route_registry.ts b/modules/angular2/src/router/route_registry.ts
index ff0165fb42ba..fa73497918b9 100644
--- a/modules/angular2/src/router/route_registry.ts
+++ b/modules/angular2/src/router/route_registry.ts
@@ -1,5 +1,6 @@
-import {RouteRecognizer, RouteMatch} from './route_recognizer';
-import {Instruction} from './instruction';
+import {PathMatch} from './path_recognizer';
+import {RouteRecognizer} from './route_recognizer';
+import {Instruction, ComponentInstruction, PrimaryInstruction} from './instruction';
import {
List,
ListWrapper,
@@ -18,12 +19,23 @@ import {
isFunction,
StringWrapper,
BaseException,
+ Type,
getTypeNameForDebugging
} from 'angular2/src/facade/lang';
-import {RouteConfig, AsyncRoute, Route, Redirect, RouteDefinition} from './route_config_impl';
+import {
+ RouteConfig,
+ AsyncRoute,
+ Route,
+ AuxRoute,
+ Redirect,
+ RouteDefinition
+} from './route_config_impl';
import {reflector} from 'angular2/src/reflection/reflection';
import {Injectable} from 'angular2/di';
import {normalizeRouteConfig} from './route_config_nomalizer';
+import {parser, Url} from './url_parser';
+
+var _resolveToNull = PromiseWrapper.resolve(null);
/**
* The RouteRegistry holds route configurations for each component in an Angular app.
@@ -37,13 +49,20 @@ export class RouteRegistry {
/**
* Given a component and a configuration object, add the route to this registry
*/
- config(parentComponent: any, config: RouteDefinition, isRootLevelRoute: boolean = false): void {
+ config(parentComponent: any, config: RouteDefinition): void {
config = normalizeRouteConfig(config);
+ // this is here because Dart type guard reasons
+ if (config instanceof Route) {
+ assertComponentExists(config.component, config.path);
+ } else if (config instanceof AuxRoute) {
+ assertComponentExists(config.component, config.path);
+ }
+
var recognizer: RouteRecognizer = this._rules.get(parentComponent);
if (isBlank(recognizer)) {
- recognizer = new RouteRecognizer(isRootLevelRoute);
+ recognizer = new RouteRecognizer();
this._rules.set(parentComponent, recognizer);
}
@@ -61,7 +80,7 @@ export class RouteRegistry {
/**
* Reads the annotations of a component and configures the registry based on them
*/
- configFromComponent(component: any, isRootComponent: boolean = false): void {
+ configFromComponent(component: any): void {
if (!isType(component)) {
return;
}
@@ -77,8 +96,7 @@ export class RouteRegistry {
var annotation = annotations[i];
if (annotation instanceof RouteConfig) {
- ListWrapper.forEach(annotation.configs,
- (config) => this.config(component, config, isRootComponent));
+ ListWrapper.forEach(annotation.configs, (config) => this.config(component, config));
}
}
}
@@ -90,63 +108,100 @@ export class RouteRegistry {
* the application into the state specified by the url
*/
recognize(url: string, parentComponent: any): Promise {
+ var parsedUrl = parser.parse(url);
+ return this._recognize(parsedUrl, parentComponent);
+ }
+
+ private _recognize(parsedUrl: Url, parentComponent): Promise {
+ return this._recognizePrimaryRoute(parsedUrl, parentComponent)
+ .then((instruction: PrimaryInstruction) =>
+ this._completeAuxiliaryRouteMatches(instruction, parentComponent));
+ }
+
+ private _recognizePrimaryRoute(parsedUrl: Url, parentComponent): Promise {
var componentRecognizer = this._rules.get(parentComponent);
if (isBlank(componentRecognizer)) {
return PromiseWrapper.resolve(null);
}
// Matches some beginning part of the given URL
- var possibleMatches = componentRecognizer.recognize(url);
- var matchPromises =
- ListWrapper.map(possibleMatches, (candidate) => this._completeRouteMatch(candidate));
+ var possibleMatches = componentRecognizer.recognize(parsedUrl);
- return PromiseWrapper.all(matchPromises)
- .then((solutions: List) => {
- // remove nulls
- var fullSolutions = ListWrapper.filter(solutions, (solution) => isPresent(solution));
+ var matchPromises =
+ ListWrapper.map(possibleMatches, (candidate) => this._completePrimaryRouteMatch(candidate));
- if (fullSolutions.length > 0) {
- return mostSpecific(fullSolutions);
- }
- return null;
- });
+ return PromiseWrapper.all(matchPromises).then(mostSpecific);
}
-
- _completeRouteMatch(partialMatch: RouteMatch): Promise {
- var recognizer = partialMatch.recognizer;
- var handler = recognizer.handler;
- return handler.resolveComponentType().then((componentType) => {
+ private _completePrimaryRouteMatch(partialMatch: PathMatch): Promise {
+ var instruction = partialMatch.instruction;
+ return instruction.resolveComponentType().then((componentType) => {
this.configFromComponent(componentType);
- if (partialMatch.unmatchedUrl.length == 0) {
- if (recognizer.terminal) {
- return new Instruction(componentType, partialMatch.matchedUrl, recognizer, null,
- partialMatch.params());
+ if (isBlank(partialMatch.remaining)) {
+ if (instruction.terminal) {
+ return new PrimaryInstruction(instruction, null, partialMatch.remainingAux);
} else {
return null;
}
}
- return this.recognize(partialMatch.unmatchedUrl, componentType)
- .then(childInstruction => {
+ return this._recognizePrimaryRoute(partialMatch.remaining, componentType)
+ .then((childInstruction) => {
if (isBlank(childInstruction)) {
return null;
} else {
- return new Instruction(componentType, partialMatch.matchedUrl, recognizer,
- childInstruction);
+ return new PrimaryInstruction(instruction, childInstruction,
+ partialMatch.remainingAux);
}
});
});
}
+
+ private _completeAuxiliaryRouteMatches(instruction: PrimaryInstruction,
+ parentComponent: any): Promise {
+ if (isBlank(instruction)) {
+ return _resolveToNull;
+ }
+
+ var componentRecognizer = this._rules.get(parentComponent);
+ var auxInstructions = {};
+
+ var promises = instruction.auxUrls.map((auxSegment: Url) => {
+ var match = componentRecognizer.recognizeAuxiliary(auxSegment);
+ if (isBlank(match)) {
+ return _resolveToNull;
+ }
+ return this._completePrimaryRouteMatch(match).then((auxInstruction: PrimaryInstruction) => {
+ if (isPresent(auxInstruction)) {
+ return this._completeAuxiliaryRouteMatches(auxInstruction, parentComponent)
+ .then((finishedAuxRoute: Instruction) => {
+ auxInstructions[auxSegment.path] = finishedAuxRoute;
+ });
+ }
+ });
+ });
+ return PromiseWrapper.all(promises).then((_) => {
+ if (isBlank(instruction.child)) {
+ return new Instruction(instruction.component, null, auxInstructions);
+ }
+ return this._completeAuxiliaryRouteMatches(instruction.child,
+ instruction.component.componentType)
+ .then((completeChild) => {
+ return new Instruction(instruction.component, completeChild, auxInstructions);
+ });
+ });
+ }
+
/**
* Given a normalized list with component names and params like: `['user', {id: 3 }]`
* generates a url with a leading slash relative to the provided `parentComponent`.
*/
- generate(linkParams: List, parentComponent: any): string {
- let url = '';
+ generate(linkParams: List, parentComponent: any): Instruction {
+ let segments = [];
let componentCursor = parentComponent;
+
for (let i = 0; i < linkParams.length; i += 1) {
let segment = linkParams[i];
if (isBlank(componentCursor)) {
@@ -172,15 +227,22 @@ export class RouteRegistry {
`Component "${getTypeNameForDebugging(componentCursor)}" has no route config.`);
}
var response = componentRecognizer.generate(segment, params);
+
if (isBlank(response)) {
throw new BaseException(
`Component "${getTypeNameForDebugging(componentCursor)}" has no route named "${segment}".`);
}
- url += response['url'];
- componentCursor = response['nextComponent'];
+ segments.push(response);
+ componentCursor = response.componentType;
}
- return url;
+ var instruction = null;
+
+ while (segments.length > 0) {
+ instruction = new Instruction(segments.pop(), instruction, {});
+ }
+
+ return instruction;
}
}
@@ -188,11 +250,17 @@ export class RouteRegistry {
/*
* Given a list of instructions, returns the most specific instruction
*/
-function mostSpecific(instructions: List): Instruction {
+function mostSpecific(instructions: List): PrimaryInstruction {
+ if (instructions.length == 0) {
+ return null;
+ }
var mostSpecificSolution = instructions[0];
for (var solutionIndex = 1; solutionIndex < instructions.length; solutionIndex++) {
- var solution = instructions[solutionIndex];
- if (solution.specificity > mostSpecificSolution.specificity) {
+ var solution: PrimaryInstruction = instructions[solutionIndex];
+ if (isBlank(solution)) {
+ continue;
+ }
+ if (solution.component.specificity > mostSpecificSolution.component.specificity) {
mostSpecificSolution = solution;
}
}
@@ -216,3 +284,9 @@ function assertTerminalComponent(component, path) {
}
}
}
+
+function assertComponentExists(component: Type, path: string): void {
+ if (!isType(component)) {
+ throw new BaseException(`Component for route "${path}" is not defined, or is not a class.`);
+ }
+}
diff --git a/modules/angular2/src/router/router.ts b/modules/angular2/src/router/router.ts
index ee6cdb1c127b..1d73035556f9 100644
--- a/modules/angular2/src/router/router.ts
+++ b/modules/angular2/src/router/router.ts
@@ -12,7 +12,7 @@ import {
import {RouteRegistry} from './route_registry';
import {Pipeline} from './pipeline';
-import {Instruction} from './instruction';
+import {ComponentInstruction, Instruction, stringifyInstruction} from './instruction';
import {RouterOutlet} from './router_outlet';
import {Location} from './location';
import {getCanActivateHook} from './route_lifecycle_reflector';
@@ -45,10 +45,9 @@ export class Router {
private _currentInstruction: Instruction = null;
private _currentNavigation: Promise = _resolveToTrue;
private _outlet: RouterOutlet = null;
+ private _auxOutlets: Map = new Map();
private _subject: EventEmitter = new EventEmitter();
- // todo(jeffbcross): rename _registry to registry since it is accessed from subclasses
- // todo(jeffbcross): rename _pipeline to pipeline since it is accessed from subclasses
constructor(public registry: RouteRegistry, public _pipeline: Pipeline, public parent: Router,
public hostComponent: any) {}
@@ -65,8 +64,11 @@ export class Router {
* you're writing a reusable component.
*/
registerOutlet(outlet: RouterOutlet): Promise {
- // TODO: sibling routes
- this._outlet = outlet;
+ if (isPresent(outlet.name)) {
+ this._auxOutlets.set(outlet.name, outlet);
+ } else {
+ this._outlet = outlet;
+ }
if (isPresent(this._currentInstruction)) {
return outlet.commit(this._currentInstruction);
}
@@ -87,9 +89,8 @@ export class Router {
* ```
*/
config(definitions: List): Promise {
- definitions.forEach((routeDefinition) => {
- this.registry.config(this.hostComponent, routeDefinition, this instanceof RootRouter);
- });
+ definitions.forEach(
+ (routeDefinition) => { this.registry.config(this.hostComponent, routeDefinition); });
return this.renavigate();
}
@@ -104,31 +105,51 @@ export class Router {
return this._currentNavigation = this._currentNavigation.then((_) => {
this.lastNavigationAttempt = url;
this._startNavigating();
- return this._afterPromiseFinishNavigating(this.recognize(url).then((matchedInstruction) => {
- if (isBlank(matchedInstruction)) {
+ return this._afterPromiseFinishNavigating(this.recognize(url).then((instruction) => {
+ if (isBlank(instruction)) {
return false;
}
- return this._reuse(matchedInstruction)
- .then((_) => this._canActivate(matchedInstruction))
- .then((result) => {
- if (!result) {
- return false;
- }
- return this._canDeactivate(matchedInstruction)
- .then((result) => {
- if (result) {
- return this.commit(matchedInstruction, _skipLocationChange)
- .then((_) => {
- this._emitNavigationFinish(matchedInstruction.accumulatedUrl);
- return true;
- });
- }
- });
- });
+ return this._navigate(instruction, _skipLocationChange);
}));
});
}
+
+ /**
+ * Navigate via the provided instruction. Returns a promise that resolves when navigation is
+ * complete.
+ */
+ navigateInstruction(instruction: Instruction,
+ _skipLocationChange: boolean = false): Promise {
+ if (isBlank(instruction)) {
+ return _resolveToFalse;
+ }
+ return this._currentNavigation = this._currentNavigation.then((_) => {
+ this._startNavigating();
+ return this._afterPromiseFinishNavigating(this._navigate(instruction, _skipLocationChange));
+ });
+ }
+
+ _navigate(instruction: Instruction, _skipLocationChange: boolean): Promise {
+ return this._reuse(instruction)
+ .then((_) => this._canActivate(instruction))
+ .then((result) => {
+ if (!result) {
+ return false;
+ }
+ return this._canDeactivate(instruction)
+ .then((result) => {
+ if (result) {
+ return this.commit(instruction, _skipLocationChange)
+ .then((_) => {
+ this._emitNavigationFinish(stringifyInstruction(instruction));
+ return true;
+ });
+ }
+ });
+ });
+ }
+
private _emitNavigationFinish(url): void { ObservableWrapper.callNext(this._subject, url); }
private _afterPromiseFinishNavigating(promise: Promise): Promise {
@@ -138,21 +159,20 @@ export class Router {
});
}
- _reuse(instruction): Promise {
+ _reuse(instruction: Instruction): Promise {
if (isBlank(this._outlet)) {
return _resolveToFalse;
}
return this._outlet.canReuse(instruction)
.then((result) => {
- instruction.reuse = result;
if (isPresent(this._outlet.childRouter) && isPresent(instruction.child)) {
return this._outlet.childRouter._reuse(instruction.child);
}
});
}
- private _canActivate(instruction: Instruction): Promise {
- return canActivateOne(instruction, this._currentInstruction);
+ private _canActivate(nextInstruction: Instruction): Promise {
+ return canActivateOne(nextInstruction, this._currentInstruction);
}
private _canDeactivate(instruction: Instruction): Promise {
@@ -160,11 +180,12 @@ export class Router {
return _resolveToTrue;
}
var next: Promise;
- if (isPresent(instruction) && instruction.reuse) {
+ if (isPresent(instruction) && instruction.component.reuse) {
next = _resolveToTrue;
} else {
next = this._outlet.canDeactivate(instruction);
}
+ // TODO: aux route lifecycle hooks
return next.then((result) => {
if (result == false) {
return false;
@@ -182,10 +203,14 @@ export class Router {
*/
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise {
this._currentInstruction = instruction;
+ var next = _resolveToTrue;
if (isPresent(this._outlet)) {
- return this._outlet.commit(instruction);
+ next = this._outlet.commit(instruction);
}
- return _resolveToTrue;
+ var promises = [];
+ MapWrapper.forEach(this._auxOutlets,
+ (outlet, _) => { promises.push(outlet.commit(instruction)); });
+ return next.then((_) => PromiseWrapper.all(promises));
}
@@ -237,7 +262,7 @@ export class Router {
* Generate a URL from a component name and optional map of parameters. The URL is relative to the
* app's base href.
*/
- generate(linkParams: List): string {
+ generate(linkParams: List): Instruction {
let normalizedLinkParams = splitAndFlattenLinkParams(linkParams);
var first = ListWrapper.first(normalizedLinkParams);
@@ -275,11 +300,22 @@ export class Router {
throw new BaseException(msg);
}
- let url = '';
- if (isPresent(router.parent) && isPresent(router.parent._currentInstruction)) {
- url = router.parent._currentInstruction.capturedUrl;
+ // TODO: structural cloning and whatnot
+
+ var url = [];
+ var parent = router.parent;
+ while (isPresent(parent)) {
+ url.unshift(parent._currentInstruction);
+ parent = parent.parent;
+ }
+
+ var nextInstruction = this.registry.generate(rest, router.hostComponent);
+
+ while (url.length > 0) {
+ nextInstruction = url.pop().replaceChild(nextInstruction);
}
- return url + '/' + this.registry.generate(rest, router.hostComponent);
+
+ return nextInstruction;
}
}
@@ -291,14 +327,19 @@ export class RootRouter extends Router {
super(registry, pipeline, null, hostComponent);
this._location = location;
this._location.subscribe((change) => this.navigate(change['url'], isPresent(change['pop'])));
- this.registry.configFromComponent(hostComponent, true);
+
+ this.registry.configFromComponent(hostComponent);
this.navigate(location.path());
}
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise {
+ var emitUrl = stringifyInstruction(instruction);
+ if (emitUrl.length > 0) {
+ emitUrl = '/' + emitUrl;
+ }
var promise = super.commit(instruction);
if (!_skipLocationChange) {
- promise = promise.then((_) => { this._location.go(instruction.accumulatedUrl); });
+ promise = promise.then((_) => { this._location.go(emitUrl); });
}
return promise;
}
@@ -315,6 +356,12 @@ class ChildRouter extends Router {
// Delegate navigation to the root router
return this.parent.navigate(url, _skipLocationChange);
}
+
+ navigateInstruction(instruction: Instruction,
+ _skipLocationChange: boolean = false): Promise {
+ // Delegate navigation to the root router
+ return this.parent.navigateInstruction(instruction, _skipLocationChange);
+ }
}
/*
@@ -332,22 +379,24 @@ function splitAndFlattenLinkParams(linkParams: List): List {
}, []);
}
-function canActivateOne(nextInstruction, currentInstruction): Promise {
+function canActivateOne(nextInstruction: Instruction, prevInstruction: Instruction):
+ Promise {
var next = _resolveToTrue;
if (isPresent(nextInstruction.child)) {
next = canActivateOne(nextInstruction.child,
- isPresent(currentInstruction) ? currentInstruction.child : null);
+ isPresent(prevInstruction) ? prevInstruction.child : null);
}
- return next.then((res) => {
- if (res == false) {
+ return next.then((result) => {
+ if (result == false) {
return false;
}
- if (nextInstruction.reuse) {
+ if (nextInstruction.component.reuse) {
return true;
}
- var hook = getCanActivateHook(nextInstruction.component);
+ var hook = getCanActivateHook(nextInstruction.component.componentType);
if (isPresent(hook)) {
- return hook(nextInstruction, currentInstruction);
+ return hook(nextInstruction.component,
+ isPresent(prevInstruction) ? prevInstruction.component : null);
}
return true;
});
diff --git a/modules/angular2/src/router/router_link.ts b/modules/angular2/src/router/router_link.ts
index 53356a4d3443..26d503169cbc 100644
--- a/modules/angular2/src/router/router_link.ts
+++ b/modules/angular2/src/router/router_link.ts
@@ -3,6 +3,7 @@ import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection'
import {Router} from './router';
import {Location} from './location';
+import {Instruction, stringifyInstruction} from './instruction';
/**
* The RouterLink directive lets you link to specific parts of your app.
@@ -43,19 +44,23 @@ export class RouterLink {
// the url displayed on the anchor element.
visibleHref: string;
- // the url passed to the router navigation.
- _navigationHref: string;
+
+ // the instruction passed to the router to navigate
+ private _navigationInstruction: Instruction;
constructor(private _router: Router, private _location: Location) {}
set routeParams(changes: List) {
this._routeParams = changes;
- this._navigationHref = this._router.generate(this._routeParams);
- this.visibleHref = this._location.normalizeAbsolutely(this._navigationHref);
+ this._navigationInstruction = this._router.generate(this._routeParams);
+
+ // TODO: is this the right spot for this?
+ var navigationHref = '/' + stringifyInstruction(this._navigationInstruction);
+ this.visibleHref = this._location.normalizeAbsolutely(navigationHref);
}
onClick(): boolean {
- this._router.navigate(this._navigationHref);
+ this._router.navigateInstruction(this._navigationInstruction);
return false;
}
}
diff --git a/modules/angular2/src/router/router_outlet.ts b/modules/angular2/src/router/router_outlet.ts
index bd9037426eca..39c707f495ea 100644
--- a/modules/angular2/src/router/router_outlet.ts
+++ b/modules/angular2/src/router/router_outlet.ts
@@ -7,7 +7,7 @@ import {DynamicComponentLoader, ComponentRef, ElementRef} from 'angular2/core';
import {Injector, bind, Dependency, undefinedValue} from 'angular2/di';
import * as routerMod from './router';
-import {Instruction, RouteParams} from './instruction';
+import {Instruction, ComponentInstruction, RouteParams} from './instruction';
import * as hookMod from './lifecycle_annotations';
import {hasLifecycleHook} from './route_lifecycle_reflector';
@@ -23,16 +23,16 @@ import {hasLifecycleHook} from './route_lifecycle_reflector';
@Directive({selector: 'router-outlet'})
export class RouterOutlet {
childRouter: routerMod.Router = null;
+ name: string = null;
private _componentRef: ComponentRef = null;
- private _currentInstruction: Instruction = null;
+ private _currentInstruction: ComponentInstruction = null;
constructor(private _elementRef: ElementRef, private _loader: DynamicComponentLoader,
private _parentRouter: routerMod.Router, @Attribute('name') nameAttr: string) {
- // TODO: reintroduce with new // sibling routes
- // if (isBlank(nameAttr)) {
- // nameAttr = 'default';
- //}
+ if (isPresent(nameAttr)) {
+ this.name = nameAttr;
+ }
this._parentRouter.registerOutlet(this);
}
@@ -40,15 +40,28 @@ export class RouterOutlet {
* Given an instruction, update the contents of this outlet.
*/
commit(instruction: Instruction): Promise {
+ instruction = this._getInstruction(instruction);
+ var componentInstruction = instruction.component;
+ if (isBlank(componentInstruction)) {
+ return PromiseWrapper.resolve(true);
+ }
var next;
- if (instruction.reuse) {
- next = this._reuse(instruction);
+ if (componentInstruction.reuse) {
+ next = this._reuse(componentInstruction);
} else {
- next = this.deactivate(instruction).then((_) => this._activate(instruction));
+ next = this.deactivate(instruction).then((_) => this._activate(componentInstruction));
}
return next.then((_) => this._commitChild(instruction));
}
+ private _getInstruction(instruction: Instruction): Instruction {
+ if (isPresent(this.name)) {
+ return instruction.auxInstruction[this.name];
+ } else {
+ return instruction;
+ }
+ }
+
private _commitChild(instruction: Instruction): Promise {
if (isPresent(this.childRouter)) {
return this.childRouter.commit(instruction.child);
@@ -57,20 +70,21 @@ export class RouterOutlet {
}
}
- private _activate(instruction: Instruction): Promise {
+ private _activate(instruction: ComponentInstruction): Promise {
var previousInstruction = this._currentInstruction;
this._currentInstruction = instruction;
- this.childRouter = this._parentRouter.childRouter(instruction.component);
+ var componentType = instruction.componentType;
+ this.childRouter = this._parentRouter.childRouter(componentType);
var bindings = Injector.resolve([
bind(RouteParams)
- .toValue(new RouteParams(instruction.params())),
+ .toValue(new RouteParams(instruction.params)),
bind(routerMod.Router).toValue(this.childRouter)
]);
- return this._loader.loadNextToLocation(instruction.component, this._elementRef, bindings)
+ return this._loader.loadNextToLocation(componentType, this._elementRef, bindings)
.then((componentRef) => {
this._componentRef = componentRef;
- if (hasLifecycleHook(hookMod.onActivate, instruction.component)) {
+ if (hasLifecycleHook(hookMod.onActivate, componentType)) {
return this._componentRef.instance.onActivate(instruction, previousInstruction);
}
});
@@ -84,9 +98,11 @@ export class RouterOutlet {
if (isBlank(this._currentInstruction)) {
return PromiseWrapper.resolve(true);
}
- if (hasLifecycleHook(hookMod.canDeactivate, this._currentInstruction.component)) {
- return PromiseWrapper.resolve(
- this._componentRef.instance.canDeactivate(nextInstruction, this._currentInstruction));
+ var outletInstruction = this._getInstruction(nextInstruction);
+ if (hasLifecycleHook(hookMod.canDeactivate, this._currentInstruction.componentType)) {
+ return PromiseWrapper.resolve(this._componentRef.instance.canDeactivate(
+ isPresent(outletInstruction) ? outletInstruction.component : null,
+ this._currentInstruction));
}
return PromiseWrapper.resolve(true);
}
@@ -97,24 +113,34 @@ export class RouterOutlet {
*/
canReuse(nextInstruction: Instruction): Promise {
var result;
+
+ var outletInstruction = this._getInstruction(nextInstruction);
+ var componentInstruction = outletInstruction.component;
+
if (isBlank(this._currentInstruction) ||
- this._currentInstruction.component != nextInstruction.component) {
+ this._currentInstruction.componentType != componentInstruction.componentType) {
result = false;
- } else if (hasLifecycleHook(hookMod.canReuse, this._currentInstruction.component)) {
- result = this._componentRef.instance.canReuse(nextInstruction, this._currentInstruction);
+ } else if (hasLifecycleHook(hookMod.canReuse, this._currentInstruction.componentType)) {
+ result = this._componentRef.instance.canReuse(componentInstruction, this._currentInstruction);
} else {
- result = nextInstruction == this._currentInstruction ||
- StringMapWrapper.equals(nextInstruction.params(), this._currentInstruction.params());
+ result =
+ componentInstruction == this._currentInstruction ||
+ (isPresent(componentInstruction.params) && isPresent(this._currentInstruction.params) &&
+ StringMapWrapper.equals(componentInstruction.params, this._currentInstruction.params));
}
- return PromiseWrapper.resolve(result);
+ return PromiseWrapper.resolve(result).then((result) => {
+ // TODO: this is a hack
+ componentInstruction.reuse = result;
+ return result;
+ });
}
- private _reuse(instruction): Promise {
+ private _reuse(instruction: ComponentInstruction): Promise {
var previousInstruction = this._currentInstruction;
this._currentInstruction = instruction;
return PromiseWrapper.resolve(
- hasLifecycleHook(hookMod.onReuse, this._currentInstruction.component) ?
+ hasLifecycleHook(hookMod.onReuse, this._currentInstruction.componentType) ?
this._componentRef.instance.onReuse(instruction, previousInstruction) :
true);
}
@@ -122,14 +148,16 @@ export class RouterOutlet {
deactivate(nextInstruction: Instruction): Promise {
+ var outletInstruction = this._getInstruction(nextInstruction);
+ var componentInstruction = isPresent(outletInstruction) ? outletInstruction.component : null;
return (isPresent(this.childRouter) ?
- this.childRouter.deactivate(isPresent(nextInstruction) ? nextInstruction.child :
- null) :
+ this.childRouter.deactivate(isPresent(outletInstruction) ? outletInstruction.child :
+ null) :
PromiseWrapper.resolve(true))
.then((_) => {
if (isPresent(this._componentRef) && isPresent(this._currentInstruction) &&
- hasLifecycleHook(hookMod.onDeactivate, this._currentInstruction.component)) {
- return this._componentRef.instance.onDeactivate(nextInstruction,
+ hasLifecycleHook(hookMod.onDeactivate, this._currentInstruction.componentType)) {
+ return this._componentRef.instance.onDeactivate(componentInstruction,
this._currentInstruction);
}
})
diff --git a/modules/angular2/src/router/url.ts b/modules/angular2/src/router/url.ts
deleted file mode 100644
index 90136eed2e69..000000000000
--- a/modules/angular2/src/router/url.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import {RegExpWrapper, StringWrapper} from 'angular2/src/facade/lang';
-
-var specialCharacters = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
-
-var escapeRe = RegExpWrapper.create('(\\' + specialCharacters.join('|\\') + ')', 'g');
-
-export function escapeRegex(string: string): string {
- return StringWrapper.replaceAllMapped(string, escapeRe, (match) => { return "\\" + match; });
-}
diff --git a/modules/angular2/src/router/url_parser.ts b/modules/angular2/src/router/url_parser.ts
new file mode 100644
index 000000000000..1e9e15c58d6a
--- /dev/null
+++ b/modules/angular2/src/router/url_parser.ts
@@ -0,0 +1,210 @@
+import {List, StringMap, StringMapWrapper} from 'angular2/src/facade/collection';
+import {
+ isPresent,
+ isBlank,
+ BaseException,
+ RegExpWrapper,
+ CONST_EXPR
+} from 'angular2/src/facade/lang';
+
+
+export class Url {
+ constructor(public path: string, public child: Url = null,
+ public auxiliary: List = CONST_EXPR([]),
+ public params: StringMap = null) {}
+
+ toString(): string {
+ return this.path + this._matrixParamsToString() + this._auxToString() + this._childString();
+ }
+
+ segmentToString(): string { return this.path + this._matrixParamsToString(); }
+
+ _auxToString(): string {
+ return this.auxiliary.length > 0 ?
+ ('(' + this.auxiliary.map(sibling => sibling.toString()).join('//') + ')') :
+ '';
+ }
+
+ private _matrixParamsToString(): string {
+ if (isBlank(this.params)) {
+ return '';
+ }
+
+ return ';' + serializeParams(this.params).join(';');
+ }
+
+ _childString(): string { return isPresent(this.child) ? ('/' + this.child.toString()) : ''; }
+}
+
+export class RootUrl extends Url {
+ constructor(path: string, child: Url = null, auxiliary: List = CONST_EXPR([]),
+ params: StringMap = null) {
+ super(path, child, auxiliary, params);
+ }
+
+ toString(): string {
+ return this.path + this._auxToString() + this._childString() + this._queryParamsToString();
+ }
+
+ segmentToString(): string { return this.path + this._queryParamsToString(); }
+
+ private _queryParamsToString(): string {
+ if (isBlank(this.params)) {
+ return '';
+ }
+
+ return '?' + serializeParams(this.params).join('&');
+ }
+}
+
+var SEGMENT_RE = RegExpWrapper.create('^[^\\/\\(\\)\\?;=&]+');
+function matchUrlSegment(str: string): string {
+ var match = RegExpWrapper.firstMatch(SEGMENT_RE, str);
+ return isPresent(match) ? match[0] : null;
+}
+
+export class UrlParser {
+ private remaining: string;
+
+ peekStartsWith(str: string): boolean { return this.remaining.startsWith(str); }
+
+ capture(str: string): void {
+ if (!this.remaining.startsWith(str)) {
+ throw new BaseException(`Expected "${str}".`);
+ }
+ this.remaining = this.remaining.substring(str.length);
+ }
+
+ parse(url: string): Url {
+ this.remaining = url;
+ if (url == '' || url == '/') {
+ return new Url('');
+ }
+ return this.parseRoot();
+ }
+
+ // segment + (aux segments) + (query params)
+ parseRoot(): Url {
+ if (this.peekStartsWith('/')) {
+ this.capture('/');
+ }
+ var path = matchUrlSegment(this.remaining);
+ this.capture(path);
+
+ var aux = [];
+ if (this.peekStartsWith('(')) {
+ aux = this.parseAuxiliaryRoutes();
+ }
+ if (this.peekStartsWith(';')) {
+ // TODO: should these params just be dropped?
+ this.parseMatrixParams();
+ }
+ var child = null;
+ if (this.peekStartsWith('/') && !this.peekStartsWith('//')) {
+ this.capture('/');
+ child = this.parseSegment();
+ }
+ var queryParams = null;
+ if (this.peekStartsWith('?')) {
+ queryParams = this.parseQueryParams();
+ }
+ return new RootUrl(path, child, aux, queryParams);
+ }
+
+ // segment + (matrix params) + (aux segments)
+ parseSegment(): Url {
+ if (this.remaining.length == 0) {
+ return null;
+ }
+ if (this.peekStartsWith('/')) {
+ this.capture('/');
+ }
+ var path = matchUrlSegment(this.remaining);
+ this.capture(path);
+
+ var matrixParams = null;
+ if (this.peekStartsWith(';')) {
+ matrixParams = this.parseMatrixParams();
+ }
+ var aux = [];
+ if (this.peekStartsWith('(')) {
+ aux = this.parseAuxiliaryRoutes();
+ }
+ var child = null;
+ if (this.peekStartsWith('/') && !this.peekStartsWith('//')) {
+ this.capture('/');
+ child = this.parseSegment();
+ }
+ return new Url(path, child, aux, matrixParams);
+ }
+
+ parseQueryParams(): StringMap {
+ var params = {};
+ this.capture('?');
+ this.parseParam(params);
+ while (this.remaining.length > 0 && this.peekStartsWith('&')) {
+ this.capture('&');
+ this.parseParam(params);
+ }
+ return params;
+ }
+
+ parseMatrixParams(): StringMap {
+ var params = {};
+ while (this.remaining.length > 0 && this.peekStartsWith(';')) {
+ this.capture(';');
+ this.parseParam(params);
+ }
+ return params;
+ }
+
+ parseParam(params: StringMap): void {
+ var key = matchUrlSegment(this.remaining);
+ if (isBlank(key)) {
+ return;
+ }
+ this.capture(key);
+ var value: any = true;
+ if (this.peekStartsWith('=')) {
+ this.capture('=');
+ var valueMatch = matchUrlSegment(this.remaining);
+ if (isPresent(valueMatch)) {
+ value = valueMatch;
+ this.capture(value);
+ }
+ }
+
+ params[key] = value;
+ }
+
+ parseAuxiliaryRoutes(): List {
+ var routes = [];
+ this.capture('(');
+
+ while (!this.peekStartsWith(')') && this.remaining.length > 0) {
+ routes.push(this.parseSegment());
+ if (this.peekStartsWith('//')) {
+ this.capture('//');
+ }
+ }
+ this.capture(')');
+
+ return routes;
+ }
+}
+
+export var parser = new UrlParser();
+
+export function serializeParams(paramMap: StringMap): List {
+ var params = [];
+ if (isPresent(paramMap)) {
+ StringMapWrapper.forEach(paramMap, (value, key) => {
+ if (value == true) {
+ params.push(key);
+ } else {
+ params.push(key + '=' + value);
+ }
+ });
+ }
+ return params;
+}
diff --git a/modules/angular2/src/services/url_resolver.ts b/modules/angular2/src/services/url_resolver.ts
index aa515e9f5631..924c9f88d835 100644
--- a/modules/angular2/src/services/url_resolver.ts
+++ b/modules/angular2/src/services/url_resolver.ts
@@ -272,7 +272,7 @@ function _joinAndCanonicalizePath(parts: List): string {
* @return {string}
*/
function _resolveUrl(base: string, url: string): string {
- var parts = _split(url);
+ var parts = _split(encodeURI(url));
var baseParts = _split(base);
if (isPresent(parts[_ComponentIndex.SCHEME])) {
diff --git a/modules/angular2/src/test_lib/spies.dart b/modules/angular2/src/test_lib/spies.dart
index 0e0cee62fb9d..e906e1aed38e 100644
--- a/modules/angular2/src/test_lib/spies.dart
+++ b/modules/angular2/src/test_lib/spies.dart
@@ -4,32 +4,22 @@ import 'package:angular2/src/change_detection/change_detection.dart';
import 'package:angular2/di.dart';
import './test_lib.dart';
-@proxy()
+@proxy
class SpyChangeDetector extends SpyObject implements ChangeDetector {
noSuchMethod(m) => super.noSuchMethod(m);
}
-@proxy()
+@proxy
class SpyProtoChangeDetector extends SpyObject implements ProtoChangeDetector {
noSuchMethod(m) => super.noSuchMethod(m);
}
-@proxy()
-class SpyPipe extends SpyObject implements Pipe {
- noSuchMethod(m) => super.noSuchMethod(m);
-}
-
-@proxy()
-class SpyPipeFactory extends SpyObject implements PipeFactory {
- noSuchMethod(m) => super.noSuchMethod(m);
-}
-
-@proxy()
+@proxy
class SpyDependencyProvider extends SpyObject implements DependencyProvider {
noSuchMethod(m) => super.noSuchMethod(m);
}
-@proxy()
+@proxy
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {
noSuchMethod(m) => super.noSuchMethod(m);
}
@@ -39,3 +29,8 @@ class SpyIterableDifferFactory extends SpyObject
implements IterableDifferFactory {
noSuchMethod(m) => super.noSuchMethod(m);
}
+
+@proxy
+class SpyInjector extends SpyObject implements Injector {
+ noSuchMethod(m) => super.noSuchMethod(m);
+}
diff --git a/modules/angular2/src/test_lib/spies.ts b/modules/angular2/src/test_lib/spies.ts
index 391bd9e1d20b..7343ac2c0c8f 100644
--- a/modules/angular2/src/test_lib/spies.ts
+++ b/modules/angular2/src/test_lib/spies.ts
@@ -1,12 +1,12 @@
import {
ChangeDetector,
+ ChangeDetectorRef,
ProtoChangeDetector,
DynamicChangeDetector
} from 'angular2/src/change_detection/change_detection';
import {DependencyProvider} from 'angular2/di';
-import {BasePipe} from 'angular2/src/change_detection/pipes/pipe';
import {SpyObject, proxy} from './test_lib';
export class SpyChangeDetector extends SpyObject {
@@ -17,12 +17,12 @@ export class SpyProtoChangeDetector extends SpyObject {
constructor() { super(DynamicChangeDetector); }
}
-export class SpyPipe extends SpyObject {
- constructor() { super(BasePipe); }
-}
-
-export class SpyPipeFactory extends SpyObject {}
-
export class SpyDependencyProvider extends SpyObject {}
export class SpyIterableDifferFactory extends SpyObject {}
+
+export class SpyInjector extends SpyObject {}
+
+export class SpyChangeDetectorRef extends SpyObject {
+ constructor() { super(ChangeDetectorRef); }
+}
diff --git a/modules/angular2/src/test_lib/test_component_builder.ts b/modules/angular2/src/test_lib/test_component_builder.ts
index a0d596e916b2..bac1d582b91c 100644
--- a/modules/angular2/src/test_lib/test_component_builder.ts
+++ b/modules/angular2/src/test_lib/test_component_builder.ts
@@ -138,6 +138,10 @@ export class TestComponentBuilder {
var doc = this._injector.get(DOCUMENT_TOKEN);
// TODO(juliemr): can/should this be optional?
+ var oldRoots = DOM.querySelectorAll(doc, '[id^=root]');
+ for (var i = 0; i < oldRoots.length; i++) {
+ DOM.remove(oldRoots[i]);
+ }
DOM.appendChild(doc.body, rootEl);
diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts
index 14294e7428bd..4c7dacfa58e4 100644
--- a/modules/angular2/src/test_lib/test_injector.ts
+++ b/modules/angular2/src/test_lib/test_injector.ts
@@ -7,17 +7,17 @@ import {
Lexer,
ChangeDetection,
DynamicChangeDetection,
- Pipes,
- defaultPipes,
IterableDiffers,
defaultIterableDiffers,
KeyValueDiffers,
defaultKeyValueDiffers
} from 'angular2/src/change_detection/change_detection';
+import {DEFAULT_PIPES} from 'angular2/pipes';
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
import {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
+import {PipeResolver} from 'angular2/src/core/compiler/pipe_resolver';
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
import {XHR} from 'angular2/src/render/xhr';
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
@@ -93,7 +93,7 @@ function _getAppBindings() {
// The document is only available in browser environment
try {
- appDoc = DOM.createHtmlDocument();
+ appDoc = DOM.defaultDoc();
} catch (e) {
appDoc = null;
}
@@ -122,7 +122,7 @@ function _getAppBindings() {
Compiler,
CompilerCache,
bind(ViewResolver).toClass(MockViewResolver),
- bind(Pipes).toValue(defaultPipes),
+ DEFAULT_PIPES,
bind(IterableDiffers).toValue(defaultIterableDiffers),
bind(KeyValueDiffers).toValue(defaultKeyValueDiffers),
bind(ChangeDetection).toClass(DynamicChangeDetection),
@@ -130,6 +130,7 @@ function _getAppBindings() {
ViewLoader,
DynamicComponentLoader,
DirectiveResolver,
+ PipeResolver,
Parser,
Lexer,
bind(ExceptionHandler).toValue(new ExceptionHandler(DOM)),
diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart
index d007a7b4e4eb..812100ed13c6 100644
--- a/modules/angular2/src/test_lib/test_lib.dart
+++ b/modules/angular2/src/test_lib/test_lib.dart
@@ -100,6 +100,7 @@ class Expect extends gns.Expect {
void toThrowError([message = ""]) => toThrowWith(message: message);
void toThrowErrorWith(message) => expectException(this.actual, message);
void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future);
+ void toHaveCssClass(className) => gns.guinness.matchers.toBeTrue(DOM.hasClass(actual, className));
void toImplement(expected) => toBeA(expected);
void toBeNaN() =>
gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0);
@@ -138,6 +139,7 @@ class NotExpect extends gns.NotExpect {
void toEqual(expected) => toHaveSameProps(expected);
void toBePromise() => gns.guinness.matchers.toBeFalse(actual is Future);
+ void toHaveCssClass(className) => gns.guinness.matchers.toBeFalse(DOM.hasClass(actual, className));
void toBeNull() => gns.guinness.matchers.toBeFalse(actual == null);
Function get _expect => gns.guinness.matchers.expect;
}
diff --git a/modules/angular2/src/test_lib/test_lib.ts b/modules/angular2/src/test_lib/test_lib.ts
index 2e29d92bc048..51b0845f5303 100644
--- a/modules/angular2/src/test_lib/test_lib.ts
+++ b/modules/angular2/src/test_lib/test_lib.ts
@@ -24,6 +24,7 @@ export interface NgMatchers extends jasmine.Matchers {
toBePromise(): boolean;
toBeAnInstanceOf(expected: any): boolean;
toHaveText(expected: any): boolean;
+ toHaveCssClass(expected: any): boolean;
toImplement(expected: any): boolean;
toContainError(expected: any): boolean;
toThrowErrorWith(expectedMessage: any): boolean;
@@ -244,6 +245,21 @@ _global.beforeEach(function() {
};
},
+ toHaveCssClass: function() {
+ return {compare: buildError(false), negativeCompare: buildError(true)};
+
+ function buildError(isNot) {
+ return function(actual, className) {
+ return {
+ pass: DOM.hasClass(actual, className) == !isNot,
+ get message() {
+ return `Expected ${actual.outerHTML} ${isNot ? 'not ' : ''}to contain the CSS class "${className}"`;
+ }
+ };
+ };
+ }
+ },
+
toContainError: function() {
return {
compare: function(actual, expectedText) {
diff --git a/modules/angular2/src/transform/reflection_remover/ast_tester.dart b/modules/angular2/src/transform/reflection_remover/ast_tester.dart
index f26431644461..cf2a25834252 100644
--- a/modules/angular2/src/transform/reflection_remover/ast_tester.dart
+++ b/modules/angular2/src/transform/reflection_remover/ast_tester.dart
@@ -13,11 +13,12 @@ class AstTester {
'${node.constructorName.type.name}' == REFLECTION_CAPABILITIES_NAME;
bool isReflectionCapabilitiesImport(ImportDirective node) {
- return node.uri.stringValue.endsWith("reflection_capabilities.dart");
+ return node.uri.stringValue ==
+ "package:angular2/src/reflection/reflection_capabilities.dart";
}
bool isBootstrapImport(ImportDirective node) {
- return node.uri.stringValue.endsWith("/bootstrap.dart");
+ return node.uri.stringValue == "package:angular2/bootstrap.dart";
}
}
diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart
index 105fa555e34f..60a4fba90f93 100644
--- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart
+++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart
@@ -269,15 +269,11 @@ class _CodegenState {
var newValue = _names.getLocalName(r.selfIndex);
var pipe = _names.getPipeName(r.selfIndex);
- var cdRef = 'this.ref';
var pipeType = r.name;
var read = '''
if ($_IDENTICAL_CHECK_FN($pipe, $_UTIL.uninitialized)) {
- $pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef);
- } else if (!$pipe.supports($context)) {
- $pipe.onDestroy();
- $pipe = ${_names.getPipesAccessorName()}.get('$pipeType', $context, $cdRef);
+ $pipe = ${_names.getPipesAccessorName()}.get('$pipeType');
}
$newValue = $pipe.transform($context, [$argString]);
''';
diff --git a/modules/angular2/src/util/decorators.ts b/modules/angular2/src/util/decorators.ts
index b9da7c39d13c..582c9e96e9e6 100644
--- a/modules/angular2/src/util/decorators.ts
+++ b/modules/angular2/src/util/decorators.ts
@@ -268,6 +268,7 @@ export function makeParamDecorator(annotationCls): any {
return ParamDecorator;
}
+
function ParamDecorator(cls, unusedKey, index): any {
var parameters: Array> = Reflect.getMetadata('parameters', cls);
parameters = parameters || [];
diff --git a/modules/angular2/src/web-workers/shared/api.ts b/modules/angular2/src/web-workers/shared/api.ts
index 0658e2d9ab6a..54b619e8caaf 100644
--- a/modules/angular2/src/web-workers/shared/api.ts
+++ b/modules/angular2/src/web-workers/shared/api.ts
@@ -2,8 +2,8 @@ import {CONST_EXPR} from "angular2/src/facade/lang";
import {OpaqueToken} from "angular2/di";
import {RenderElementRef, RenderViewRef} from "angular2/src/render/api";
-export const ON_WEBWORKER = CONST_EXPR(new OpaqueToken('WebWorker.onWebWorker'));
+export const ON_WEB_WORKER = CONST_EXPR(new OpaqueToken('WebWorker.onWebWorker'));
-export class WorkerElementRef implements RenderElementRef {
+export class WebWorkerElementRef implements RenderElementRef {
constructor(public renderView: RenderViewRef, public renderBoundElementIndex: number) {}
}
diff --git a/modules/angular2/src/web-workers/shared/message_bus.ts b/modules/angular2/src/web-workers/shared/message_bus.ts
index e14278622e58..8f40e7673248 100644
--- a/modules/angular2/src/web-workers/shared/message_bus.ts
+++ b/modules/angular2/src/web-workers/shared/message_bus.ts
@@ -17,7 +17,7 @@ export interface MessageBusSource {
/**
* Attaches the SourceListener to this source.
* The SourceListener will get called whenever the bus receives a message
- * Returns a listener id that can be passed to {@link removeListener}
+ * Returns a listener id that can be passed to {removeListener}
*/
addListener(fn: SourceListener): number;
removeListener(index: number);
diff --git a/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts b/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts
index 5accac9c7a71..ef46bc0dcf8e 100644
--- a/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts
+++ b/modules/angular2/src/web-workers/shared/render_proto_view_ref_store.ts
@@ -1,6 +1,6 @@
import {Injectable, Inject} from "angular2/di";
import {RenderProtoViewRef} from "angular2/src/render/api";
-import {ON_WEBWORKER} from "angular2/src/web-workers/shared/api";
+import {ON_WEB_WORKER} from "angular2/src/web-workers/shared/api";
@Injectable()
export class RenderProtoViewRefStore {
@@ -10,7 +10,7 @@ export class RenderProtoViewRefStore {
private _nextIndex: number = 0;
private _onWebworker: boolean;
- constructor(@Inject(ON_WEBWORKER) onWebworker) { this._onWebworker = onWebworker; }
+ constructor(@Inject(ON_WEB_WORKER) onWebworker) { this._onWebworker = onWebworker; }
storeRenderProtoViewRef(ref: RenderProtoViewRef): number {
if (this._lookupByProtoView.has(ref)) {
@@ -32,7 +32,7 @@ export class RenderProtoViewRefStore {
}
if (this._onWebworker) {
- return new WebworkerRenderProtoViewRef(index);
+ return new WebWorkerRenderProtoViewRef(index);
} else {
return this.retreiveRenderProtoViewRef(index);
}
@@ -44,13 +44,13 @@ export class RenderProtoViewRefStore {
}
if (this._onWebworker) {
- return (ref).refNumber;
+ return (ref).refNumber;
} else {
return this.storeRenderProtoViewRef(ref);
}
}
}
-export class WebworkerRenderProtoViewRef extends RenderProtoViewRef {
+export class WebWorkerRenderProtoViewRef extends RenderProtoViewRef {
constructor(public refNumber: number) { super(); }
}
diff --git a/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts b/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts
index 62f829e131de..a7d7ef48d639 100644
--- a/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts
+++ b/modules/angular2/src/web-workers/shared/render_view_with_fragments_store.ts
@@ -1,6 +1,6 @@
import {Injectable, Inject} from "angular2/di";
import {RenderViewRef, RenderFragmentRef, RenderViewWithFragments} from "angular2/src/render/api";
-import {ON_WEBWORKER} from "angular2/src/web-workers/shared/api";
+import {ON_WEB_WORKER} from "angular2/src/web-workers/shared/api";
import {List, ListWrapper} from "angular2/src/facade/collection";
@Injectable()
@@ -10,7 +10,7 @@ export class RenderViewWithFragmentsStore {
private _lookupByIndex: Map;
private _lookupByView: Map;
- constructor(@Inject(ON_WEBWORKER) onWebWorker) {
+ constructor(@Inject(ON_WEB_WORKER) onWebWorker) {
this._onWebWorker = onWebWorker;
this._lookupByIndex = new Map();
this._lookupByView = new Map();
@@ -19,11 +19,11 @@ export class RenderViewWithFragmentsStore {
allocate(fragmentCount: number): RenderViewWithFragments {
var initialIndex = this._nextIndex;
- var viewRef = new WorkerRenderViewRef(this._nextIndex++);
+ var viewRef = new WebWorkerRenderViewRef(this._nextIndex++);
var fragmentRefs = ListWrapper.createGrowableSize(fragmentCount);
for (var i = 0; i < fragmentCount; i++) {
- fragmentRefs[i] = new WorkerRenderFragmentRef(this._nextIndex++);
+ fragmentRefs[i] = new WebWorkerRenderFragmentRef(this._nextIndex++);
}
var renderViewWithFragments = new RenderViewWithFragments(viewRef, fragmentRefs);
this.store(renderViewWithFragments, initialIndex);
@@ -79,7 +79,7 @@ export class RenderViewWithFragmentsStore {
}
if (this._onWebWorker) {
- return (ref).serialize();
+ return (ref).serialize();
} else {
return this._lookupByView.get(ref);
}
@@ -92,7 +92,7 @@ export class RenderViewWithFragmentsStore {
if (this._onWebWorker) {
return {
- 'viewRef': (view.viewRef).serialize(),
+ 'viewRef': (view.viewRef).serialize(),
'fragmentRefs': ListWrapper.map(view.fragmentRefs, (val) => val.serialize())
};
} else {
@@ -116,19 +116,21 @@ export class RenderViewWithFragmentsStore {
}
}
-export class WorkerRenderViewRef extends RenderViewRef {
+export class WebWorkerRenderViewRef extends RenderViewRef {
constructor(public refNumber: number) { super(); }
serialize(): number { return this.refNumber; }
- static deserialize(ref: number): WorkerRenderViewRef { return new WorkerRenderViewRef(ref); }
+ static deserialize(ref: number): WebWorkerRenderViewRef {
+ return new WebWorkerRenderViewRef(ref);
+ }
}
-export class WorkerRenderFragmentRef extends RenderFragmentRef {
+export class WebWorkerRenderFragmentRef extends RenderFragmentRef {
constructor(public refNumber: number) { super(); }
serialize(): number { return this.refNumber; }
- static deserialize(ref: number): WorkerRenderFragmentRef {
- return new WorkerRenderFragmentRef(ref);
+ static deserialize(ref: number): WebWorkerRenderFragmentRef {
+ return new WebWorkerRenderFragmentRef(ref);
}
}
diff --git a/modules/angular2/src/web-workers/shared/serializer.ts b/modules/angular2/src/web-workers/shared/serializer.ts
index 5767649f4ff3..0c472d4195bf 100644
--- a/modules/angular2/src/web-workers/shared/serializer.ts
+++ b/modules/angular2/src/web-workers/shared/serializer.ts
@@ -31,7 +31,7 @@ import {
ViewEncapsulation,
PropertyBindingType
} from "angular2/src/render/api";
-import {WorkerElementRef} from 'angular2/src/web-workers/shared/api';
+import {WebWorkerElementRef} from 'angular2/src/web-workers/shared/api';
import {AST, ASTWithSource} from 'angular2/src/change_detection/change_detection';
import {Parser} from "angular2/src/change_detection/parser/parser";
import {Injectable} from "angular2/di";
@@ -99,7 +99,7 @@ export class Serializer {
return this._renderViewStore.serializeRenderViewRef(obj);
} else if (type == RenderFragmentRef) {
return this._renderViewStore.serializeRenderFragmentRef(obj);
- } else if (type == WorkerElementRef) {
+ } else if (type == WebWorkerElementRef) {
return this._serializeWorkerElementRef(obj);
} else if (type == ElementPropertyBinding) {
return this._serializeElementPropertyBinding(obj);
@@ -143,7 +143,7 @@ export class Serializer {
return this._renderViewStore.deserializeRenderViewRef(map);
} else if (type == RenderFragmentRef) {
return this._renderViewStore.deserializeRenderFragmentRef(map);
- } else if (type == WorkerElementRef) {
+ } else if (type == WebWorkerElementRef) {
return this._deserializeWorkerElementRef(map);
} else if (type == EventBinding) {
return this._deserializeEventBinding(map);
@@ -219,8 +219,8 @@ export class Serializer {
}
private _deserializeWorkerElementRef(map: StringMap): RenderElementRef {
- return new WorkerElementRef(this.deserialize(map['renderView'], RenderViewRef),
- map['renderBoundElementIndex']);
+ return new WebWorkerElementRef(this.deserialize(map['renderView'], RenderViewRef),
+ map['renderBoundElementIndex']);
}
private _serializeRenderProtoViewMergeMapping(mapping: RenderProtoViewMergeMapping): Object {
diff --git a/modules/angular2/src/web-workers/ui/application.dart b/modules/angular2/src/web-workers/ui/application.dart
index f40da987262a..595540e22ded 100644
--- a/modules/angular2/src/web-workers/ui/application.dart
+++ b/modules/angular2/src/web-workers/ui/application.dart
@@ -13,16 +13,17 @@ import 'package:angular2/src/web-workers/ui/impl.dart' show bootstrapUICommon;
* You instantiate a WebWorker application by calling bootstrap with the URI of your worker's index script
* Note: The WebWorker script must call bootstrapWebworker once it is set up to complete the bootstrapping process
*/
-void bootstrap(String uri) {
- spawnWorker(Uri.parse(uri)).then((bus) {
+Future bootstrap(String uri) {
+ return spawnWebWorker(Uri.parse(uri)).then((bus) {
bootstrapUICommon(bus);
+ return bus;
});
}
/**
* To be called from the main thread to spawn and communicate with the worker thread
*/
-Future spawnWorker(Uri uri) {
+Future spawnWebWorker(Uri uri) {
var receivePort = new ReceivePort();
var isolateEndSendPort = receivePort.sendPort;
return Isolate.spawnUri(uri, const [], isolateEndSendPort).then((_) {
diff --git a/modules/angular2/src/web-workers/ui/application.ts b/modules/angular2/src/web-workers/ui/application.ts
index 6d7cced8aee8..8dcfa1b30b25 100644
--- a/modules/angular2/src/web-workers/ui/application.ts
+++ b/modules/angular2/src/web-workers/ui/application.ts
@@ -15,14 +15,15 @@ import {bootstrapUICommon} from "angular2/src/web-workers/ui/impl";
* Note: The WebWorker script must call bootstrapWebworker once it is set up to complete the
* bootstrapping process
*/
-export function bootstrap(uri: string): void {
- var messageBus = spawnWorker(uri);
+export function bootstrap(uri: string): MessageBus {
+ var messageBus = spawnWebWorker(uri);
bootstrapUICommon(messageBus);
+ return messageBus;
}
-export function spawnWorker(uri: string): MessageBus {
- var worker: Worker = new Worker(uri);
- return new UIMessageBus(new UIMessageBusSink(worker), new UIMessageBusSource(worker));
+export function spawnWebWorker(uri: string): MessageBus {
+ var webWorker: Worker = new Worker(uri);
+ return new UIMessageBus(new UIMessageBusSink(webWorker), new UIMessageBusSource(webWorker));
}
export class UIMessageBus implements MessageBus {
@@ -30,19 +31,19 @@ export class UIMessageBus implements MessageBus {
}
export class UIMessageBusSink implements MessageBusSink {
- constructor(private _worker: Worker) {}
+ constructor(private _webWorker: Worker) {}
- send(message: Object): void { this._worker.postMessage(message); }
+ send(message: Object): void { this._webWorker.postMessage(message); }
}
export class UIMessageBusSource implements MessageBusSource {
private _listenerStore: Map = new Map();
private _numListeners: int = 0;
- constructor(private _worker: Worker) {}
+ constructor(private _webWorker: Worker) {}
public addListener(fn: SourceListener): int {
- this._worker.addEventListener("message", fn);
+ this._webWorker.addEventListener("message", fn);
this._listenerStore[++this._numListeners] = fn;
return this._numListeners;
}
diff --git a/modules/angular2/src/web-workers/ui/di_bindings.ts b/modules/angular2/src/web-workers/ui/di_bindings.ts
index f00514c71a27..cf9ac9060d7d 100644
--- a/modules/angular2/src/web-workers/ui/di_bindings.ts
+++ b/modules/angular2/src/web-workers/ui/di_bindings.ts
@@ -10,10 +10,9 @@ import {
ChangeDetection,
DynamicChangeDetection,
JitChangeDetection,
- PreGeneratedChangeDetection,
- Pipes,
- defaultPipes
+ PreGeneratedChangeDetection
} from 'angular2/src/change_detection/change_detection';
+import {DEFAULT_PIPES} from 'angular2/pipes';
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
@@ -56,7 +55,7 @@ import {Testability} from 'angular2/src/core/testability/testability';
import {XHR} from 'angular2/src/render/xhr';
import {XHRImpl} from 'angular2/src/render/xhr_impl';
import {Serializer} from 'angular2/src/web-workers/shared/serializer';
-import {ON_WEBWORKER} from 'angular2/src/web-workers/shared/api';
+import {ON_WEB_WORKER} from 'angular2/src/web-workers/shared/api';
import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_proto_view_ref_store';
import {
RenderViewWithFragmentsStore
@@ -101,7 +100,7 @@ function _injectorBindings(): List> {
DomSharedStylesHost,
bind(SharedStylesHost).toAlias(DomSharedStylesHost),
Serializer,
- bind(ON_WEBWORKER).toValue(false),
+ bind(ON_WEB_WORKER).toValue(false),
bind(ElementSchemaRegistry).toValue(new DomElementSchemaRegistry()),
RenderViewWithFragmentsStore,
RenderProtoViewRefStore,
@@ -114,7 +113,7 @@ function _injectorBindings(): List> {
Compiler,
CompilerCache,
ViewResolver,
- bind(Pipes).toValue(defaultPipes),
+ DEFAULT_PIPES,
bind(ChangeDetection).toClass(bestChangeDetection),
ViewLoader,
DirectiveResolver,
diff --git a/modules/angular2/src/web-workers/ui/event_serializer.dart b/modules/angular2/src/web-workers/ui/event_serializer.dart
index ebe2a1312244..78d9c5783f0b 100644
--- a/modules/angular2/src/web-workers/ui/event_serializer.dart
+++ b/modules/angular2/src/web-workers/ui/event_serializer.dart
@@ -80,9 +80,9 @@ Map serializeGenericEvent(dynamic e) {
// TODO(jteplitz602): Allow users to specify the properties they need rather than always
// adding value #3374
-Map serializeEventWithValue(dynamic e) {
+Map serializeEventWithTarget(dynamic e) {
var serializedEvent = serializeEvent(e, EVENT_PROPERTIES);
- return addValue(e, serializedEvent);
+ return addTarget(e, serializedEvent);
}
Map serializeMouseEvent(dynamic e) {
@@ -91,13 +91,16 @@ Map serializeMouseEvent(dynamic e) {
Map serializeKeyboardEvent(dynamic e) {
var serializedEvent = serializeEvent(e, KEYBOARD_EVENT_PROPERTIES);
- return addValue(e, serializedEvent);
+ return addTarget(e, serializedEvent);
}
// TODO(jteplitz602): #3374. See above.
-Map addValue(dynamic e, Map serializedEvent) {
+Map addTarget(dynamic e, Map serializedEvent) {
if (NODES_WITH_VALUE.contains(e.target.tagName.toLowerCase())) {
serializedEvent['target'] = {'value': e.target.value};
+ if (e.target is InputElement) {
+ serializedEvent['target']['files'] = e.target.files;
+ }
}
return serializedEvent;
}
diff --git a/modules/angular2/src/web-workers/ui/event_serializer.ts b/modules/angular2/src/web-workers/ui/event_serializer.ts
index 5ee913e901e5..8be40065c3eb 100644
--- a/modules/angular2/src/web-workers/ui/event_serializer.ts
+++ b/modules/angular2/src/web-workers/ui/event_serializer.ts
@@ -1,4 +1,5 @@
import {StringMap, Set} from 'angular2/src/facade/collection';
+import {isPresent} from 'angular2/src/facade/lang';
const MOUSE_EVENT_PROPERTIES = [
"altKey",
@@ -41,10 +42,10 @@ export function serializeGenericEvent(e: Event): StringMap {
}
// TODO(jteplitz602): Allow users to specify the properties they need rather than always
-// adding value #3374
-export function serializeEventWithValue(e: Event): StringMap {
+// adding value and files #3374
+export function serializeEventWithTarget(e: Event): StringMap {
var serializedEvent = serializeEvent(e, EVENT_PROPERTIES);
- return addValue(e, serializedEvent);
+ return addTarget(e, serializedEvent);
}
export function serializeMouseEvent(e: MouseEvent): StringMap {
@@ -53,13 +54,17 @@ export function serializeMouseEvent(e: MouseEvent): StringMap {
export function serializeKeyboardEvent(e: KeyboardEvent): StringMap {
var serializedEvent = serializeEvent(e, KEYBOARD_EVENT_PROPERTIES);
- return addValue(e, serializedEvent);
+ return addTarget(e, serializedEvent);
}
// TODO(jteplitz602): #3374. See above.
-function addValue(e: Event, serializedEvent: StringMap): StringMap {
+function addTarget(e: Event, serializedEvent: StringMap): StringMap {
if (NODES_WITH_VALUE.has((e.target).tagName.toLowerCase())) {
- serializedEvent['target'] = {'value': (e.target).value};
+ var target = e.target;
+ serializedEvent['target'] = {'value': target.value};
+ if (isPresent(target.files)) {
+ serializedEvent['target']['files'] = target.files;
+ }
}
return serializedEvent;
}
diff --git a/modules/angular2/src/web-workers/ui/impl.ts b/modules/angular2/src/web-workers/ui/impl.ts
index 835e17a32964..490eecda173c 100644
--- a/modules/angular2/src/web-workers/ui/impl.ts
+++ b/modules/angular2/src/web-workers/ui/impl.ts
@@ -27,7 +27,7 @@ import {
RenderViewWithFragmentsStore
} from 'angular2/src/web-workers/shared/render_view_with_fragments_store';
import {createNgZone} from 'angular2/src/core/application_common';
-import {WorkerElementRef} from 'angular2/src/web-workers/shared/api';
+import {WebWorkerElementRef} from 'angular2/src/web-workers/shared/api';
import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url';
import {Injectable} from 'angular2/di';
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
@@ -35,8 +35,9 @@ import {
serializeMouseEvent,
serializeKeyboardEvent,
serializeGenericEvent,
- serializeEventWithValue
+ serializeEventWithTarget
} from 'angular2/src/web-workers/ui/event_serializer';
+import {wtfInit} from 'angular2/src/profile/wtf_init';
/**
* Creates a zone, sets up the DI bindings
@@ -45,10 +46,11 @@ import {
export function bootstrapUICommon(bus: MessageBus) {
BrowserDomAdapter.makeCurrent();
var zone = createNgZone();
+ wtfInit();
zone.run(() => {
var injector = createInjector(zone);
var webWorkerMain = injector.get(WebWorkerMain);
- webWorkerMain.attachToWorker(bus);
+ webWorkerMain.attachToWebWorker(bus);
});
}
@@ -68,21 +70,21 @@ export class WebWorkerMain {
* This instance will now listen for all messages from the worker and handle them appropriately
* Note: Don't attach more than one WebWorkerMain instance to the same MessageBus.
*/
- attachToWorker(bus: MessageBus) {
+ attachToWebWorker(bus: MessageBus) {
this._bus = bus;
- this._bus.source.addListener((message) => { this._handleWorkerMessage(message); });
+ this._bus.source.addListener((message) => { this._handleWebWorkerMessage(message); });
}
- private _sendInitMessage() { this._sendWorkerMessage("init", {"rootUrl": this._rootUrl}); }
+ private _sendInitMessage() { this._sendWebWorkerMessage("init", {"rootUrl": this._rootUrl}); }
/*
* Sends an error back to the worker thread in response to an opeartion on the UI thread
*/
- private _sendWorkerError(id: string, error: any) {
- this._sendWorkerMessage("error", {"error": error}, id);
+ private _sendWebWorkerError(id: string, error: any) {
+ this._sendWebWorkerMessage("error", {"error": error}, id);
}
- private _sendWorkerMessage(type: string, value: StringMap, id?: string) {
+ private _sendWebWorkerMessage(type: string, value: StringMap, id?: string) {
this._bus.sink.send({'type': type, 'id': id, 'value': value});
}
@@ -93,17 +95,17 @@ export class WebWorkerMain {
case "compileHost":
var directiveMetadata = this._serializer.deserialize(data.args[0], DirectiveMetadata);
promise = this._renderCompiler.compileHost(directiveMetadata);
- this._wrapWorkerPromise(data.id, promise, ProtoViewDto);
+ this._wrapWebWorkerPromise(data.id, promise, ProtoViewDto);
break;
case "compile":
var view = this._serializer.deserialize(data.args[0], ViewDefinition);
promise = this._renderCompiler.compile(view);
- this._wrapWorkerPromise(data.id, promise, ProtoViewDto);
+ this._wrapWebWorkerPromise(data.id, promise, ProtoViewDto);
break;
case "mergeProtoViewsRecursively":
var views = this._serializer.deserialize(data.args[0], RenderProtoViewRef);
promise = this._renderCompiler.mergeProtoViewsRecursively(views);
- this._wrapWorkerPromise(data.id, promise, RenderProtoViewMergeMapping);
+ this._wrapWebWorkerPromise(data.id, promise, RenderProtoViewMergeMapping);
break;
default:
throw new BaseException("not implemented");
@@ -143,7 +145,7 @@ export class WebWorkerMain {
this._renderer.attachFragmentAfterFragment(previousFragment, fragment);
break;
case "attachFragmentAfterElement":
- var element = this._serializer.deserialize(args[0], WorkerElementRef);
+ var element = this._serializer.deserialize(args[0], WebWorkerElementRef);
var fragment = this._serializer.deserialize(args[1], RenderFragmentRef);
this._renderer.attachFragmentAfterElement(element, fragment);
break;
@@ -166,31 +168,31 @@ export class WebWorkerMain {
this._renderer.setText(viewRef, textNodeIndex, text);
break;
case "setElementProperty":
- var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
+ var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef);
var propName = args[1];
var propValue = args[2];
this._renderer.setElementProperty(elementRef, propName, propValue);
break;
case "setElementAttribute":
- var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
+ var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef);
var attributeName = args[1];
var attributeValue = args[2];
this._renderer.setElementAttribute(elementRef, attributeName, attributeValue);
break;
case "setElementClass":
- var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
+ var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef);
var className = args[1];
var isAdd = args[2];
this._renderer.setElementClass(elementRef, className, isAdd);
break;
case "setElementStyle":
- var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
+ var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef);
var styleName = args[1];
var styleValue = args[2];
this._renderer.setElementStyle(elementRef, styleName, styleValue);
break;
case "invokeElementMethod":
- var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
+ var elementRef = this._serializer.deserialize(args[0], WebWorkerElementRef);
var methodName = args[1];
var methodArgs = args[2];
this._renderer.invokeElementMethod(elementRef, methodName, methodArgs);
@@ -206,7 +208,7 @@ export class WebWorkerMain {
}
// TODO(jteplitz602): Create message type enum #3044
- private _handleWorkerMessage(message: StringMap