diff --git a/ng-sample/app/app.ts b/ng-sample/app/app.ts
index 34faafe71..ad35174c2 100644
--- a/ng-sample/app/app.ts
+++ b/ng-sample/app/app.ts
@@ -7,15 +7,23 @@
// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { nativeScriptBootstrap } from "./nativescript-angular/application";
+import { NS_ROUTER_PROVIDERS, routerTraceCategory } from "./nativescript-angular/router/ns-router";
+
+import trace = require("trace");
+trace.setCategories(routerTraceCategory);
+trace.enable();
//import {RendererTest} from './examples/renderer-test';
//import {Benchmark} from './performance/benchmark';
//import {ListTest} from './examples/list/list-test';
-import {ListTestAsync} from "./examples/list/list-test-async";
+// import {ListTestAsync} from "./examples/list/list-test-async";
// import {ImageTest} from "./examples/image/image-test";
+import {NavigationTest} from "./examples/navigation/navigation-test";
+
//nativeScriptBootstrap(RendererTest);
//nativeScriptBootstrap(Benchmark);
//nativeScriptBootstrap(ListTest);
-nativeScriptBootstrap(ListTestAsync);
+// nativeScriptBootstrap(ListTestAsync);
// nativeScriptBootstrap(ImageTest);
+nativeScriptBootstrap(NavigationTest, [NS_ROUTER_PROVIDERS]);
diff --git a/ng-sample/app/examples/navigation/nav-component.ts b/ng-sample/app/examples/navigation/nav-component.ts
new file mode 100644
index 000000000..2ca51246f
--- /dev/null
+++ b/ng-sample/app/examples/navigation/nav-component.ts
@@ -0,0 +1,53 @@
+import {Component} from 'angular2/core';
+import {OnActivate, OnDeactivate, LocationStrategy, RouteParams, ComponentInstruction } from 'angular2/router';
+import {topmost} from "ui/frame";
+import {NS_ROUTER_DIRECTIVES} from "../../nativescript-angular/router/ns-router";
+
+
+@Component({
+ selector: 'example-group',
+ directives: [NS_ROUTER_DIRECTIVES],
+ template: `
+
+
+
+
+
+
+
+
+
+`
+})
+export class NavComponent implements OnActivate, OnDeactivate {
+ static counter: number = 0;
+
+ public compId: number;
+ public depth: number;
+
+ constructor(params: RouteParams, private location: LocationStrategy) {
+ NavComponent.counter++;
+
+ this.compId = NavComponent.counter;
+ this.depth = parseInt(params.get("depth"));
+
+ console.log("NavComponent.constructor() componenetID: " + this.compId)
+ }
+
+ public goBack() {
+ // this.location.back();
+ topmost().goBack();
+ }
+
+ routerOnActivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
+ console.log("NavComponent.routerOnActivate() componenetID: " + this.compId)
+ }
+
+ routerOnDeactivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
+ console.log("NavComponent.routerOnDeactivate() componenetID: " + this.compId)
+ }
+}
\ No newline at end of file
diff --git a/ng-sample/app/examples/navigation/navigation-test.ts b/ng-sample/app/examples/navigation/navigation-test.ts
new file mode 100644
index 000000000..5c90487fe
--- /dev/null
+++ b/ng-sample/app/examples/navigation/navigation-test.ts
@@ -0,0 +1,29 @@
+import {Component} from 'angular2/core';
+import {RouteConfig, ROUTER_PROVIDERS, ROUTER_DIRECTIVES} from 'angular2/router';
+
+import {NavComponent} from "./nav-component";
+import {NS_ROUTER_DIRECTIVES} from "../../nativescript-angular/router/ns-router";
+
+@Component({
+ selector:"start-nav-test",
+ directives: [NS_ROUTER_DIRECTIVES],
+ template:``
+})
+class StartComponent {
+
+}
+
+
+@Component({
+ selector: 'navigation-test',
+ directives: [NS_ROUTER_DIRECTIVES],
+ template: ""
+})
+@RouteConfig([
+ { path: '/', component: StartComponent, as: 'Start' },
+ { path: '/nav/:depth', component: NavComponent, as: 'Nav' },
+])
+export class NavigationTest {
+
+}
+
diff --git a/src/nativescript-angular/router/common.ts b/src/nativescript-angular/router/common.ts
new file mode 100644
index 000000000..2ec488458
--- /dev/null
+++ b/src/nativescript-angular/router/common.ts
@@ -0,0 +1,7 @@
+import trace = require("trace");
+
+export const CATEGORY = "ns-router";
+
+export function log(message: string) {
+ trace.write(message, CATEGORY);
+}
diff --git a/src/nativescript-angular/router/ns-location-strategy.ts b/src/nativescript-angular/router/ns-location-strategy.ts
new file mode 100644
index 000000000..03973c765
--- /dev/null
+++ b/src/nativescript-angular/router/ns-location-strategy.ts
@@ -0,0 +1,90 @@
+import application = require("application");
+import { LocationStrategy } from 'angular2/router';
+import { NgZone, ApplicationRef, Inject, forwardRef } from 'angular2/core';
+import { log } from "./common";
+
+interface LocationState
+{
+ state: any,
+ title: string,
+ url: string,
+ queryParams: string
+}
+
+export class NSLocationStrategy extends LocationStrategy {
+ private states = new Array();
+ private popStateCallbacks = new Array<(_: any) => any>();
+ private ngZone: NgZone;
+ constructor(@Inject(forwardRef(() => NgZone)) zone: NgZone){
+ super();
+
+ this.ngZone = zone;
+ //if(application.android){
+ //application.android.on("activityBackPressed", (args: application.AndroidActivityBackPressedEventData) => {
+ //this.ngZone.run( () => {
+ //if(this.states.length > 1){
+ //this.back();
+ //args.cancel = true;
+ //}
+ //});
+ //})
+ //}
+ }
+
+ path(): string {
+ log("NSLocationStrategy.path()");
+ if(this.states.length > 0){
+ return this.states[this.states.length - 1].url;
+ }
+ return "/";
+ }
+ prepareExternalUrl(internal: string): string {
+ log("NSLocationStrategy.prepareExternalUrl() internal: " + internal);
+ return internal;
+ }
+ pushState(state: any, title: string, url: string, queryParams: string): void {
+ log(`NSLocationStrategy.pushState state: ${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
+
+ this.states.push({
+ state: state,
+ title: title,
+ url: url,
+ queryParams: queryParams });
+
+ }
+ replaceState(state: any, title: string, url: string, queryParams: string): void {
+ log(`NSLocationStrategy.replaceState state: ${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
+
+ this.states.pop()
+ this.states.push({
+ state: state,
+ title: title,
+ url: url,
+ queryParams: queryParams });
+ }
+ forward(): void {
+ log("NSLocationStrategy.forward");
+ throw new Error("Not implemented");
+ }
+ back(): void {
+ log("NSLocationStrategy.back");
+
+ var state = this.states.pop();
+ this.callPopState(state, true);
+ }
+ onPopState(fn: (_: any) => any): void {
+ log("NSLocationStrategy.onPopState");
+ this.popStateCallbacks.push(fn);
+ }
+ getBaseHref(): string {
+ log("NSLocationStrategy.getBaseHref()");
+ return "";
+ }
+
+ private callPopState(state:LocationState, pop: boolean = true){
+ var change = { url: state.url, pop: pop};
+ for(var fn of this.popStateCallbacks){
+ fn(change);
+ }
+ }
+}
diff --git a/src/nativescript-angular/router/ns-router-link.ts b/src/nativescript-angular/router/ns-router-link.ts
new file mode 100644
index 000000000..bb18988a8
--- /dev/null
+++ b/src/nativescript-angular/router/ns-router-link.ts
@@ -0,0 +1,60 @@
+import {Directive, Input} from 'angular2/core';
+import {isString} from 'angular2/src/facade/lang';
+import {Router, Location, Instruction} from 'angular2/router';
+import { log } from "./common";
+
+/**
+ * The NSRouterLink directive lets you link to specific parts of your app.
+ *
+ * Consider the following route configuration:
+ * ```
+ * @RouteConfig([
+ * { path: '/user', component: UserCmp, as: 'User' }
+ * ]);
+ * class MyComp {}
+ * ```
+ *
+ * When linking to this `User` route, you can write:
+ *
+ * ```
+ * link to user component
+ * ```
+ *
+ * RouterLink expects the value to be an array of route names, followed by the params
+ * for that level of routing. For instance `['/Team', {teamId: 1}, 'User', {userId: 2}]`
+ * means that we want to generate a link for the `Team` route with params `{teamId: 1}`,
+ * and with a child route `User` with params `{userId: 2}`.
+ *
+ * The first route name should be prepended with `/`, `./`, or `../`.
+ * If the route begins with `/`, the router will look up the route from the root of the app.
+ * If the route begins with `./`, the router will instead look in the current component's
+ * children for the route. And if the route begins with `../`, the router will look at the
+ * current component's parent.
+ */
+@Directive({
+ selector: '[nsRouterLink]',
+ inputs: ['params: nsRouterLink'],
+ host: {
+ '(tap)': 'onTap()'
+ }
+})
+export class NSRouterLink {
+ private _routeParams: any[];
+
+ // the instruction passed to the router to navigate
+ private _navigationInstruction: Instruction;
+
+ constructor(private _router: Router, private _location: Location) { }
+
+ // get isRouteActive(): boolean { return this._router.isRouteActive(this._navigationInstruction); }
+
+ set params(changes: any[]) {
+ this._routeParams = changes;
+ this._navigationInstruction = this._router.generate(this._routeParams);
+ }
+
+ onTap(): void {
+ log("NSRouterLink onTap() instruction: " + JSON.stringify(this._navigationInstruction))
+ this._router.navigateByInstruction(this._navigationInstruction);
+ }
+}
\ No newline at end of file
diff --git a/src/nativescript-angular/router/ns-router.ts b/src/nativescript-angular/router/ns-router.ts
new file mode 100644
index 000000000..d6bee22bc
--- /dev/null
+++ b/src/nativescript-angular/router/ns-router.ts
@@ -0,0 +1,19 @@
+import {Type} from 'angular2/src/facade/lang';
+import {NSRouterLink} from './ns-router-link';
+import {PageRouterOutlet} from './page-router-outlet';
+import {NSLocationStrategy} from './ns-location-strategy';
+import {ROUTER_PROVIDERS, LocationStrategy} from 'angular2/router';
+import {provide} from 'angular2/core';
+import { CATEGORY } from "./common";
+
+export const NS_ROUTER_PROVIDERS: any[] = [
+ ROUTER_PROVIDERS,
+ provide(LocationStrategy, {useClass: NSLocationStrategy})
+];
+
+export const NS_ROUTER_DIRECTIVES: Type[] = [
+ NSRouterLink,
+ PageRouterOutlet
+];
+
+export const routerTraceCategory = CATEGORY;
\ No newline at end of file
diff --git a/src/nativescript-angular/router/page-router-outlet.ts b/src/nativescript-angular/router/page-router-outlet.ts
new file mode 100644
index 000000000..e4a620c4f
--- /dev/null
+++ b/src/nativescript-angular/router/page-router-outlet.ts
@@ -0,0 +1,316 @@
+import {PromiseWrapper} from 'angular2/src/facade/async';
+import {isBlank, isPresent} from 'angular2/src/facade/lang';
+
+import {Directive, Attribute, DynamicComponentLoader, ComponentRef, ElementRef,
+ Injector, provide, Type, Component, OpaqueToken, Inject} from 'angular2/core';
+
+import * as routerHooks from 'angular2/src/router/lifecycle_annotations';
+import { hasLifecycleHook} from 'angular2/src/router/route_lifecycle_reflector';
+
+import { ComponentInstruction, RouteParams, RouteData, RouterOutlet, LocationStrategy, Router,
+ OnActivate, OnDeactivate } from 'angular2/router';
+
+import { topmost } from "ui";
+import { Page, NavigatedData } from "ui/page";
+import { log } from "./common";
+
+let COMPONENT = new OpaqueToken("COMPONENT");
+let _resolveToTrue = PromiseWrapper.resolve(true);
+let _resolveToFalse = PromiseWrapper.resolve(false);
+
+/**
+ * Reference Cache
+ */
+class RefCache {
+ private cache: Array = new Array();
+
+ public push(comp: ComponentRef) {
+ this.cache.push(comp);
+ }
+
+ public pop(): ComponentRef {
+ return this.cache.pop();
+ }
+
+ public peek(): ComponentRef {
+ return this.cache[this.cache.length - 1];
+ }
+}
+
+var _isGoingBack = false;
+function startGoBack() {
+ log("startGoBack()");
+ if (_isGoingBack) {
+ throw new Error("Calling startGoBack while going back.")
+ }
+ _isGoingBack = true;
+}
+
+function endGoBack() {
+ log("endGoBack()");
+ if (!_isGoingBack) {
+ throw new Error("Calling endGoBack while not going back.")
+ }
+ _isGoingBack = false;
+}
+
+function isGoingBack() {
+ return _isGoingBack;
+}
+
+
+/**
+ * A router outlet that does page navigation in NativeScript
+ *
+ * ## Use
+ *
+ * ```
+ *
+ * ```
+ */
+@Directive({ selector: 'page-router-outlet' })
+export class PageRouterOutlet extends RouterOutlet {
+ private isInitalPage: boolean = true;
+ private refCache: RefCache = new RefCache();
+
+ private componentRef: ComponentRef = null;
+ private currentComponentType: ComponentRef = null;
+ private currentInstruction: ComponentInstruction = null;
+
+ constructor(private elementRef: ElementRef,
+ private loader: DynamicComponentLoader,
+ private parentRouter: Router,
+ @Attribute('name') nameAttr: string) {
+ super(elementRef, loader, parentRouter, nameAttr)
+ }
+
+ /**
+ * Called by the Router to instantiate a new component during the commit phase of a navigation.
+ * This method in turn is responsible for calling the `routerOnActivate` hook of its child.
+ */
+ activate(nextInstruction: ComponentInstruction): Promise {
+ this.log("activate", nextInstruction);
+
+ let previousInstruction = this.currentInstruction;
+ let componentType = nextInstruction.componentType;
+ this.currentInstruction = nextInstruction;
+
+ if (isGoingBack()) {
+ log("PageRouterOutlet.activate() - Back naviation, so load from cache: " + componentType.name);
+
+ endGoBack();
+
+ // Get Component form ref and just call the activate hook
+ this.componentRef = this.refCache.peek();
+ this.currentComponentType = componentType;
+ this.checkComponentRef(this.componentRef, nextInstruction);
+
+ if (hasLifecycleHook(routerHooks.routerOnActivate, componentType)) {
+ return (this.componentRef.instance)
+ .routerOnActivate(nextInstruction, previousInstruction);
+ }
+ }
+ else {
+ let childRouter = this.parentRouter.childRouter(componentType);
+ let providers = Injector.resolve([
+ provide(RouteData, { useValue: nextInstruction.routeData }),
+ provide(RouteParams, { useValue: new RouteParams(nextInstruction.params) }),
+ provide(Router, { useValue: childRouter }),
+ provide(COMPONENT, { useValue: componentType }),
+ ]);
+
+ // TODO: Is there a better way to check first load?
+ if (this.isInitalPage) {
+ log("PageRouterOutlet.activate() inital page - just load component: " + componentType.name);
+ this.isInitalPage = false;
+ }
+ else {
+ log("PageRouterOutlet.activate() forward navigation - wrap component in page: " + componentType.name);
+ componentType = PageShim;
+ }
+
+ return this.loader.loadNextToLocation(componentType, this.elementRef, providers)
+ .then((componentRef) => {
+ this.componentRef = componentRef;
+ this.currentComponentType = componentType;
+ this.refCache.push(componentRef);
+
+ if (hasLifecycleHook(routerHooks.routerOnActivate, componentType)) {
+ return (this.componentRef.instance)
+ .routerOnActivate(nextInstruction, previousInstruction);
+ }
+ });
+ }
+ }
+
+ /**
+ * Called by the {@link Router} when an outlet disposes of a component's contents.
+ * This method in turn is responsible for calling the `routerOnDeactivate` hook of its child.
+ */
+ deactivate(nextInstruction: ComponentInstruction): Promise {
+ this.log("deactivate", nextInstruction);
+ var instruction = this.currentInstruction;
+ var compType = this.currentComponentType;
+
+ var next = _resolveToTrue;
+ if (isPresent(this.componentRef) &&
+ isPresent(instruction) &&
+ isPresent(compType) &&
+ hasLifecycleHook(routerHooks.routerOnDeactivate, compType)) {
+ next = PromiseWrapper.resolve(
+ (this.componentRef.instance).routerOnDeactivate(nextInstruction, this.currentInstruction));
+ }
+
+ if (isGoingBack()) {
+ log("PageRouterOutlet.deactivate() while going back - should dispose: " + instruction.componentType.name)
+ return next.then((_) => {
+ let popedRef = this.refCache.pop();
+
+ if (this.componentRef !== popedRef) {
+ throw new Error("Current componentRef is different for cached componentRef");
+ }
+ this.checkComponentRef(popedRef, instruction);
+
+ if (isPresent(this.componentRef)) {
+ this.componentRef.dispose();
+ this.componentRef = null;
+ }
+ });
+ }
+ else {
+ return next;
+ }
+ }
+
+ /**
+ * Called by the {@link Router} during recognition phase of a navigation.
+ * PageRouterOutlet will aways return true as cancelling navigation
+ * is currently not supported in NativeScript.
+ */
+ routerCanDeactivate(nextInstruction: ComponentInstruction): Promise {
+ return _resolveToTrue;
+ }
+
+ /**
+ * Called by the {@link Router} during recognition phase of a navigation.
+ * For PageRouterOutlet it always reurns false, as there is no way to reuse
+ * the same componenet between two pages.
+ */
+ routerCanReuse(nextInstruction: ComponentInstruction): Promise {
+ return _resolveToFalse;
+ }
+
+ /**
+ * Called by the {@link Router} during the commit phase of a navigation when an outlet
+ * reuses a component between different routes.
+ * For PageRouterOutlet this method should never be called,
+ * because routerCanReuse always returns false.
+ */
+ reuse(nextInstruction: ComponentInstruction): Promise {
+ throw new Error("reuse() method should never be called for PageRouterOutlet.")
+ return _resolveToFalse;
+ }
+
+ private checkComponentRef(popedRef: ComponentRef, instruction: ComponentInstruction) {
+ if (popedRef.instance instanceof PageShim) {
+ var shim = popedRef.instance;
+ if (shim.componentType !== instruction.componentType) {
+ throw new Error("ComponentRef value is different form expected!");
+ }
+ }
+ }
+
+ private log(method: string, nextInstruction: ComponentInstruction) {
+ log("PageRouterOutlet." + method + " isBack: " + isGoingBack() + " nextUrl: " + nextInstruction.urlPath);
+ }
+}
+
+@Component({
+ selector: 'nativescript-page-shim',
+ template: `
+
+
+
+ `
+})
+class PageShim implements OnActivate, OnDeactivate {
+ private static pageShimCount: number = 0;
+ private id: number;
+ private isInitialized: boolean;
+ private componentRef: ComponentRef;
+
+ constructor(
+ private element: ElementRef,
+ private loader: DynamicComponentLoader,
+ private locationStrategy: LocationStrategy,
+ @Inject(COMPONENT) public componentType: Type
+ ) {
+ this.id = PageShim.pageShimCount++;
+ this.log("constructor");
+ }
+
+ routerOnActivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
+ this.log("routerOnActivate");
+ let result = PromiseWrapper.resolve(true);
+
+ // On first activation:
+ // 1. Load componenet using loadIntoLocation.
+ // 2. Hijack its native element.
+ // 3. Put that element into a new page and navigate to it.
+ if (!this.isInitialized) {
+ result = new Promise((resolve, reject) => {
+ this.isInitialized = true;
+ this.loader.loadIntoLocation(this.componentType, this.element, 'content')
+ .then((componentRef) => {
+ this.componentRef = componentRef;
+
+ //Component loaded. Find its root native view.
+ const viewContainer = this.componentRef.location.nativeElement;
+ //Remove from original native parent.
+ //TODO: assuming it's a Layout.
+ (viewContainer.parent).removeChild(viewContainer);
+
+ topmost().navigate({
+ animated: true,
+ create: () => {
+ const page = new Page();
+ page.on('loaded', () => {
+ // Finish activation when page is fully loaded.
+ resolve()
+ });
+
+ page.on('navigatingFrom', global.zone.bind((args: NavigatedData) => {
+ if (args.isBackNavigation) {
+ startGoBack();
+ this.locationStrategy.back();
+ }
+ }));
+
+ // Add to new page.
+ page.content = viewContainer;
+ return page;
+ }
+ });
+ });
+ });
+ }
+
+ if (hasLifecycleHook(routerHooks.routerOnActivate, this.componentType)) {
+ result = result.then(() => {
+ return (this.componentRef.instance).routerOnActivate(nextInstruction, prevInstruction);
+ });
+ }
+ return result;
+ }
+
+ routerOnDeactivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
+ this.log("routerOnDeactivate");
+ if (hasLifecycleHook(routerHooks.routerOnDeactivate, this.componentType)) {
+ return (this.componentRef.instance).routerOnDeactivate(nextInstruction, prevInstruction);
+ }
+ }
+
+ private log(methodName: string) {
+ log("PageShim(" + this.id + ")." + methodName)
+ }
+}
\ No newline at end of file