diff --git a/ng-sample/app/examples/navigation/navigation-test.ts b/ng-sample/app/examples/navigation/navigation-test.ts
index 088d05708..691081505 100644
--- a/ng-sample/app/examples/navigation/navigation-test.ts
+++ b/ng-sample/app/examples/navigation/navigation-test.ts
@@ -31,11 +31,7 @@ class StartComponent {
@Component({
selector: 'navigation-test',
directives: [NS_ROUTER_DIRECTIVES],
- template: `
-
-
-
- `
+ template: ``
})
@RouteConfig([
{ path: '/', component: StartComponent, as: 'Start' },
@@ -44,4 +40,3 @@ class StartComponent {
export class NavigationTest {
}
-
diff --git a/src/nativescript-angular/element-registry.ts b/src/nativescript-angular/element-registry.ts
index 7e78c6aa7..d63469dde 100644
--- a/src/nativescript-angular/element-registry.ts
+++ b/src/nativescript-angular/element-registry.ts
@@ -99,3 +99,5 @@ registerElement("TextView", () => require("ui/text-view").TextView);
registerElement("TimePicker", () => require("ui/time-picker").TimePicker);
registerElement("WebView", () => require("ui/web-view").WebView);
registerElement("WrapLayout", () => require("ui/layouts/wrap-layout").WrapLayout);
+
+registerElement("DetachedContainer", () => require("ui/proxy-view-container").ProxyViewContainer, { skipAddToDom: true });
\ 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
index c4cc4be53..4ca8063e1 100644
--- a/src/nativescript-angular/router/page-router-outlet.ts
+++ b/src/nativescript-angular/router/page-router-outlet.ts
@@ -2,7 +2,7 @@ import {PromiseWrapper} from 'angular2/src/facade/async';
import {isBlank, isPresent} from 'angular2/src/facade/lang';
import {StringMapWrapper} from 'angular2/src/facade/collection';
-import {Directive, Attribute, DynamicComponentLoader, ComponentRef, ElementRef,
+import {Attribute, DynamicComponentLoader, ComponentRef, ElementRef,
Injector, provide, Type, Component, OpaqueToken, Inject} from 'angular2/core';
import * as routerHooks from 'angular2/src/router/lifecycle/lifecycle_annotations';
@@ -19,14 +19,11 @@ import {Page, NavigatedData} from "ui/page";
import {log} from "./common";
import {NSLocationStrategy} from "./ns-location-strategy";
-
-let COMPONENT = new OpaqueToken("COMPONENT");
let _resolveToTrue = PromiseWrapper.resolve(true);
-let _resolveToFalse = PromiseWrapper.resolve(false);
-
interface CacheItem {
componentRef: ComponentRef;
+ pageShimRef?: ComponentRef;
router: Router;
}
@@ -36,8 +33,8 @@ interface CacheItem {
class RefCache {
private cache: Array = new Array();
- public push(comp: ComponentRef, router: Router) {
- this.cache.push({ componentRef: comp, router: router });
+ public push(comp: ComponentRef, router: Router, pageShimRef?: ComponentRef) {
+ this.cache.push({ componentRef: comp, router: router, pageShimRef: pageShimRef });
}
public pop(): CacheItem {
@@ -49,6 +46,30 @@ class RefCache {
}
}
+/**
+ * Page shim used for loadin compnenets when navigating
+ */
+@Component({
+ selector: 'nativescript-page-shim',
+ template: `
+
+
+
+ `
+})
+class PageShim {
+ constructor(
+ private element: ElementRef,
+ private loader: DynamicComponentLoader
+ ) {
+ }
+
+ public loadComponent(componentType: Type): Promise {
+ return this.loader.loadIntoLocation(componentType, this.element, 'loader');
+ }
+}
+
+
/**
* A router outlet that does page navigation in NativeScript
*
@@ -58,13 +79,18 @@ class RefCache {
*
* ```
*/
-@Directive({ selector: 'page-router-outlet' })
+@Component({
+ selector: 'page-router-outlet',
+ template: `
+
+
+ `
+})
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,
@@ -81,58 +107,104 @@ export class PageRouterOutlet extends RouterOutlet {
*/
activate(nextInstruction: ComponentInstruction): Promise {
this.log("activate", nextInstruction);
-
let previousInstruction = this.currentInstruction;
- let componentType = nextInstruction.componentType;
this.currentInstruction = nextInstruction;
if (this.location.isPageNavigatingBack()) {
- log("PageRouterOutlet.activate() - Back naviation, so load from cache: " + componentType.name);
+ return this.activateOnGoBack(nextInstruction, previousInstruction);
+ } else {
+ return this.activateOnGoForward(nextInstruction, previousInstruction);
+ }
+ }
+
+ private activateOnGoBack(nextInstruction: ComponentInstruction, previousInstruction: ComponentInstruction): Promise {
+ log("PageRouterOutlet.activate() - Back naviation, so load from cache: " + nextInstruction.componentType.name);
- this.location.finishBackPageNavigation();
-
- // Get Component form ref and just call the activate hook
- let cacheItem = this.refCache.peek();
- this.componentRef = cacheItem.componentRef;
- this.replaceChildRouter(cacheItem.router);
+ this.location.finishBackPageNavigation();
- this.currentComponentType = componentType;
- this.checkComponentRef(this.componentRef, nextInstruction);
+ // Get Component form ref and just call the activate hook
+ let cacheItem = this.refCache.peek();
+ this.componentRef = cacheItem.componentRef;
+ this.replaceChildRouter(cacheItem.router);
+
+ if (hasLifecycleHook(routerHooks.routerOnActivate, this.componentRef.componentType)) {
+ return (this.componentRef.instance)
+ .routerOnActivate(nextInstruction, previousInstruction);
+ }
+ }
+
+ private activateOnGoForward(nextInstruction: ComponentInstruction, previousInstruction: ComponentInstruction): Promise {
+ let componentType = nextInstruction.componentType;
+ let resultPromise: Promise;
+ let pageShimRef: ComponentRef = undefined;
+ const childRouter = this.parentRouter.childRouter(componentType);
+
+ const providersArray = [
+ provide(RouteData, { useValue: nextInstruction.routeData }),
+ provide(RouteParams, { useValue: new RouteParams(nextInstruction.params) }),
+ provide(Router, { useValue: childRouter }),
+ ];
+
+ if (this.isInitalPage) {
+ log("PageRouterOutlet.activate() inital page - just load component: " + componentType.name);
+ this.isInitalPage = false;
+ resultPromise = this.loader.loadNextToLocation(componentType, this.elementRef, Injector.resolve(providersArray));
+ } else {
+ log("PageRouterOutlet.activate() forward navigation - create page shim in the loader container: " + componentType.name);
+
+ const page = new Page();
+ providersArray.push(provide(Page, { useValue: page }));
+ resultPromise = this.loader.loadIntoLocation(PageShim, this.elementRef, "loader", Injector.resolve(providersArray))
+ .then((pageComponentRef) => {
+ pageShimRef = pageComponentRef;
+ return (pageShimRef.instance).loadComponent(componentType);
+ })
+ .then((actualCoponenetRef) => {
+ return this.loadComponentInPage(page, actualCoponenetRef);
+ })
+ }
+
+ return resultPromise.then((componentRef) => {
+ this.componentRef = componentRef;
+ this.refCache.push(componentRef, childRouter, pageShimRef);
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, childRouter);
-
- if (hasLifecycleHook(routerHooks.routerOnActivate, componentType)) {
- return (this.componentRef.instance)
- .routerOnActivate(nextInstruction, previousInstruction);
- }
- });
+ private loadComponentInPage(page: Page, componentRef: ComponentRef): Promise {
+ //Component loaded. Find its root native view.
+ const componenetView = componentRef.location.nativeElement;
+ //Remove it from original native parent.
+ if (componenetView.parent) {
+ (componenetView.parent).removeChild(componenetView);
}
+ //Add it to the new page
+ page.content = componenetView;
+
+ this.location.navigateToNewPage();
+ return new Promise((resolve, reject) => {
+ page.on('loaded', () => {
+ // Finish activation when page is fully loaded.
+ resolve(componentRef)
+ });
+
+ page.on('navigatingFrom', (global).zone.bind((args: NavigatedData) => {
+ if (args.isBackNavigation) {
+ this.location.beginBackPageNavigation();
+ this.location.back();
+ }
+ }));
+
+ topmost().navigate({
+ animated: true,
+ create: () => { return page; }
+ });
+ });
}
/**
@@ -142,13 +214,11 @@ export class PageRouterOutlet extends RouterOutlet {
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)) {
+ hasLifecycleHook(routerHooks.routerOnDeactivate, this.componentRef.componentType)) {
next = PromiseWrapper.resolve(
(this.componentRef.instance).routerOnDeactivate(nextInstruction, this.currentInstruction));
}
@@ -156,17 +226,21 @@ export class PageRouterOutlet extends RouterOutlet {
if (this.location.isPageNavigatingBack()) {
log("PageRouterOutlet.deactivate() while going back - should dispose: " + instruction.componentType.name)
return next.then((_) => {
- let popedRef = this.refCache.pop().componentRef;
+ const popedItem = this.refCache.pop();
+ const popedRef = popedItem.componentRef;
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;
}
+
+ if (isPresent(popedItem.pageShimRef)) {
+ popedItem.pageShimRef.dispose();
+ }
});
} else {
return next;
@@ -232,142 +306,21 @@ export class PageRouterOutlet extends RouterOutlet {
}
return PromiseWrapper.resolve(
- hasLifecycleHook(routerHooks.routerOnReuse, this.currentComponentType) ?
+ hasLifecycleHook(routerHooks.routerOnReuse, this.componentRef.componentType) ?
(this.componentRef.instance).routerOnReuse(nextInstruction, previousInstruction) : true);
}
- 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 replaceChildRouter(childRouter: Router) {
// HACKY HACKY HACKY
// When navigationg back - we need to set the child router of
// our router - with the one we have created for the previosus page.
// Otherwise router-outlets inside that page wont't work.
// Curretly there is no other way to do that (parentRouter.childRouter() will create ne router).
-
+
this.parentRouter["_childRouter"] = childRouter;
}
private log(method: string, nextInstruction: ComponentInstruction) {
log("PageRouterOutlet." + method + " isBack: " + this.location.isPageNavigatingBack() + " nextUrl: " + nextInstruction.urlPath);
}
-}
-
-@Component({
- selector: 'nativescript-page-shim',
- template: `
-
-
-
- `
-})
-class PageShim implements OnActivate, OnDeactivate, CanReuse, OnReuse {
- private static pageShimCount: number = 0;
- private id: number;
- private isInitialized: boolean;
- private componentRef: ComponentRef;
- private page: Page;
-
- constructor(
- private element: ElementRef,
- private loader: DynamicComponentLoader,
- private locationStrategy: NSLocationStrategy,
- @Inject(COMPONENT) public componentType: Type
- ) {
- this.id = PageShim.pageShimCount++;
- this.log("constructor");
- this.page = new Page();
- }
-
- 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;
-
- let providers = Injector.resolve([
- provide(Page, { useValue: this.page }),
- ]);
-
- this.loader.loadIntoLocation(this.componentType, this.element, 'content', providers)
- .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);
-
- this.locationStrategy.navigateToNewPage();
- topmost().navigate({
- animated: true,
- create: () => {
- const page = this.page;
- page.on('loaded', () => {
- // Finish activation when page is fully loaded.
- resolve()
- });
-
- page.on('navigatingFrom', (global).zone.bind((args: NavigatedData) => {
- if (args.isBackNavigation) {
- this.locationStrategy.beginBackPageNavigation();
- 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);
- }
- }
-
- routerCanReuse(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
- this.log("routerCanReuse");
- if (hasLifecycleHook(routerHooks.routerCanReuse, this.componentType)) {
- return (this.componentRef.instance).routerCanReuse(nextInstruction, prevInstruction);
- }
- }
-
- routerOnReuse(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
- this.log("routerOnReuse");
- if (hasLifecycleHook(routerHooks.routerOnReuse, this.componentType)) {
- return (this.componentRef.instance).routerOnReuse(nextInstruction, prevInstruction);
- }
- }
-
- private log(methodName: string) {
- log("PageShim(" + this.id + ")." + methodName)
- }
-}
+}
\ No newline at end of file
diff --git a/tests/app/tests/router.ts b/tests/app/tests/router.ts
new file mode 100644
index 000000000..2333a0956
--- /dev/null
+++ b/tests/app/tests/router.ts
@@ -0,0 +1,244 @@
+//make sure you import mocha-config before angular2/core
+import {assert} from "./test-config";
+import {
+ Type,
+ Component,
+ ComponentRef,
+ DynamicComponentLoader,
+ ViewChild,
+ ElementRef,
+ provide,
+ ApplicationRef,
+ ChangeDetectorRef
+} from "angular2/core";
+
+import {ProxyViewContainer} from "ui/proxy-view-container";
+import {dumpView} from "./test-utils";
+import {bootstrapTestApp, destroyTestApp} from "./test-app";
+
+import {ROUTER_DIRECTIVES, Router, OnActivate, OnDeactivate, CanReuse, OnReuse,
+ LocationStrategy, RouteParams, ComponentInstruction, RouteConfig, Location } from 'angular2/router';
+import {topmost, BackstackEntry} from "ui/frame";
+import {Page} from "ui/page";
+import {NS_ROUTER_DIRECTIVES, NS_ROUTER_PROVIDERS} from "../nativescript-angular/router/ns-router";
+
+@Component({
+ template: ``
+})
+export class LayoutWithLabel {
+ constructor(public elementRef: ElementRef) { }
+}
+
+
+const hooksLog = [];
+class CompBase implements OnActivate, OnDeactivate {
+ protected name: string = "";
+
+ routerOnActivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
+ this.log("activate", nextInstruction, prevInstruction);
+ }
+
+ routerOnDeactivate(nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction): any {
+ this.log("deactivate", nextInstruction, prevInstruction);
+ }
+
+ private log(method: string, nextInstruction: ComponentInstruction, prevInstruction: ComponentInstruction) {
+ hooksLog.push(this.name + "." + method + " " + nextInstruction.urlPath + " " + (prevInstruction ? prevInstruction.urlPath : null));
+ }
+}
+
+@Component({
+ selector: "first-comp",
+ template: ``
+})
+export class FirstComponent extends CompBase {
+ protected name = "first";
+}
+
+@Component({
+ selector: "second-comp",
+ template: ``
+})
+export class SecondComponent extends CompBase {
+ protected name = "second";
+}
+
+@Component({
+ selector: "outlet-app",
+ directives: [ROUTER_DIRECTIVES],
+ template: ``
+
+})
+@RouteConfig([
+ { path: '/first', name: 'First', component: FirstComponent, useAsDefault: true },
+ { path: '/second', name: 'Second', component: SecondComponent }
+])
+export class SimpleOutletCompnenet {
+ constructor(
+ public elementRef: ElementRef,
+ public router: Router,
+ public location: LocationStrategy) {
+ }
+}
+
+@Component({
+ selector: "page-outlet-app",
+ directives: [ROUTER_DIRECTIVES, NS_ROUTER_DIRECTIVES],
+ template: ``
+
+})
+@RouteConfig([
+ { path: '/first', name: 'First', component: FirstComponent, useAsDefault: true },
+ { path: '/second', name: 'Second', component: SecondComponent }
+])
+export class PageOutletCompnenet {
+ constructor(
+ public elementRef: ElementRef,
+ public router: Router,
+ public location: LocationStrategy) {
+ }
+}
+
+describe('router-outlet', () => {
+ let testApp: SimpleOutletCompnenet = null;
+
+ beforeEach((done) => {
+ hooksLog.length = 0;
+ return bootstrapTestApp(SimpleOutletCompnenet, [NS_ROUTER_PROVIDERS]).then((app: SimpleOutletCompnenet) => {
+ testApp = app;
+ setTimeout(done, 0);
+ });
+ });
+
+ afterEach(() => {
+ destroyTestApp(testApp)
+ });
+
+
+ it("loads default path", () => {
+ assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))", dumpView(testApp.elementRef.nativeElement, true));
+ });
+
+ it("navigates to other component", () => {
+ return testApp.router.navigateByUrl("/second").then(() => {
+ assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=Second])))", dumpView(testApp.elementRef.nativeElement, true));
+ })
+ });
+
+ it("navigates to other component and then comes back", () => {
+ return testApp.router.navigateByUrl("/second").then(() => {
+ return testApp.router.navigateByUrl("/first");
+ }).then(() => {
+ assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))", dumpView(testApp.elementRef.nativeElement, true));
+ })
+ });
+
+ it("hooks are fired when navigating", () => {
+ return testApp.router.navigateByUrl("/second").then(() => {
+ return testApp.router.navigateByUrl("/first");
+ }).then(() => {
+ var expected = [
+ "first.activate first null",
+ "first.deactivate second first",
+ "second.activate second first",
+ "second.deactivate first second",
+ "first.activate first second"];
+
+ assert.equal(hooksLog.join("|"), expected.join("|"));
+ })
+ });
+});
+
+describe('page-router-outlet', () => {
+ let testApp: PageOutletCompnenet = null;
+ var initialBackstackLength: number;
+ var initalPage: Page;
+
+ before(() => {
+ initialBackstackLength = topmost().backStack.length;
+ initalPage = topmost().currentPage;
+ })
+
+ beforeEach((done) => {
+ hooksLog.length = 0;
+
+ bootstrapTestApp(PageOutletCompnenet, [NS_ROUTER_PROVIDERS]).then((app: PageOutletCompnenet) => {
+ testApp = app;
+ setTimeout(done, 0);
+ });
+ });
+
+ afterEach(() => {
+ destroyTestApp(testApp)
+
+ // Ensure navigation to inital page
+ const backStack = topmost().backStack;
+ if (backStack.length > initialBackstackLength) {
+ return goBackToEntry(backStack[initialBackstackLength]);
+ }
+ else {
+ return true;
+ }
+ });
+
+ function goBackToEntry(entry: BackstackEntry): Promise {
+ var navPromise = getNavigatedPromise(entry.resolvedPage);
+ topmost().goBack(entry);
+ return navPromise;
+ }
+
+ function getNavigatedPromise(page: Page): Promise {
+ return new Promise((resolve, reject) => {
+ var callback = () => {
+ page.off("navigatedTo", callback);
+ setTimeout(resolve, 0);
+ }
+ page.on("navigatedTo", callback)
+ })
+ }
+
+ it("loads default path", () => {
+ // App-Root app-componenet first-componenet
+ // | | |
+ var expected = "(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))";
+ assert.equal(expected, dumpView(testApp.elementRef.nativeElement, true));
+ });
+
+ it("navigates to other component", () => {
+ return testApp.router.navigateByUrl("/second")
+ .then(() => {
+ assert.equal("(ProxyViewContainer (Label[text=Second]))", dumpView(topmost().currentPage.content, true));
+ })
+ });
+
+ it("navigates to other component and then comes back", () => {
+ return testApp.router.navigateByUrl("/second")
+ .then(() => {
+ var navPromise = getNavigatedPromise(initalPage);
+ testApp.location.back();
+ return navPromise;
+ }).then(() => {
+ assert.equal(topmost().currentPage, initalPage);
+ assert.equal("(ROOT (ProxyViewContainer), (ProxyViewContainer (Label[text=First])))", dumpView(testApp.elementRef.nativeElement, true));
+ })
+ });
+
+
+ it("hooks are fired when navigating", () => {
+ return testApp.router.navigateByUrl("/second")
+ .then(() => {
+ var navPromise = getNavigatedPromise(initalPage);
+ testApp.location.back();
+ return navPromise;
+ }).then(() => {
+ var expected = [
+ "first.activate first null",
+ "first.deactivate second first",
+ "second.activate second first",
+ "second.deactivate first second",
+ "first.activate first second"];
+
+ assert.equal(hooksLog.join("|"), expected.join("|"));
+ })
+ });
+});
diff --git a/tests/app/tests/test-app.ts b/tests/app/tests/test-app.ts
index 477517ac1..3f4e4a6e3 100644
--- a/tests/app/tests/test-app.ts
+++ b/tests/app/tests/test-app.ts
@@ -1,18 +1,10 @@
//make sure you import mocha-config before angular2/core
-import {bootstrap} from "../nativescript-angular/application";
-import {
- Type,
- Component,
- ComponentRef,
- DynamicComponentLoader,
- ViewChild,
- ElementRef,
- provide,
- ApplicationRef
+import {bootstrap, ProviderArray} from "../nativescript-angular/application";
+import {Type, Component, ComponentRef, DynamicComponentLoader,
+ ViewChild, ElementRef, provide, ApplicationRef
} from "angular2/core";
import {View} from "ui/core/view";
-import {StackLayout} from "ui/layouts/stack-layout";
import {GridLayout} from "ui/layouts/grid-layout";
import {LayoutBase} from "ui/layouts/layout-base";
import {topmost} from 'ui/frame';
@@ -24,8 +16,6 @@ import {APP_ROOT_VIEW} from "../nativescript-angular/platform-providers";
})
export class TestApp {
@ViewChild("loadSite") public loadSiteRef: ElementRef;
- private _pageRoot: LayoutBase;
- private _appRoot: StackLayout;
private _pendingDispose: ComponentRef[] = [];
constructor(public loader: DynamicComponentLoader,
@@ -49,27 +39,45 @@ export class TestApp {
}
public static create(): Promise {
- const page = topmost().currentPage;
- const rootLayout = page.content;
- const viewRoot = new StackLayout();
- rootLayout.addChild(viewRoot);
- GridLayout.setRow(rootLayout, 50);
- const rootViewProvider = provide(APP_ROOT_VIEW, { useFactory: () => viewRoot });
- return bootstrap(TestApp, [rootViewProvider]).then((componentRef) => {
- const testApp = componentRef.instance;
- testApp._pageRoot = rootLayout;
- testApp._appRoot = viewRoot;
- return testApp;
- });
+ return bootstrapTestApp(TestApp);
}
public dispose() {
- if (!this._appRoot) {
- throw new Error("Test app already disposed or not initalized.");
- }
this.disposeComponenets();
- this._pageRoot.removeChild(this._appRoot);
- this._appRoot = null;
- this._pageRoot = null;
+ destroyTestApp(this);
}
-}
\ No newline at end of file
+}
+
+var runningApps = new Map();
+
+export function bootstrapTestApp(appComponentType: any, providers: ProviderArray = []): Promise {
+ const page = topmost().currentPage;
+ const rootLayout = page.content;
+ const viewRoot = new GridLayout();
+ rootLayout.addChild(viewRoot);
+ GridLayout.setRow(rootLayout, 50);
+
+ const rootViewProvider = provide(APP_ROOT_VIEW, { useValue: viewRoot });
+ return bootstrap(appComponentType, providers.concat(rootViewProvider)).then((componentRef) => {
+ componentRef.injector.get(ApplicationRef)
+ const testApp = componentRef.instance;
+
+ runningApps.set(testApp, {
+ hostView: rootLayout,
+ appRoot: viewRoot,
+ appRef: componentRef.injector.get(ApplicationRef) });
+
+ return testApp;
+ });
+}
+
+export function destroyTestApp(app: any) {
+ if (!runningApps.has(app)) {
+ throw new Error("Unable to cleanup app: " + app);
+ }
+
+ var entry = runningApps.get(app);
+ entry.hostView.removeChild(entry.appRoot);
+ entry.appRef.dispose();
+ runningApps.delete(app);
+}
diff --git a/tests/app/tests/test-utils.ts b/tests/app/tests/test-utils.ts
index 37f32bc5c..2426c82f4 100644
--- a/tests/app/tests/test-utils.ts
+++ b/tests/app/tests/test-utils.ts
@@ -11,7 +11,7 @@ function getChildren(view: View): Array {
}
export function dumpView(view: View, verbose: boolean = false): string {
- let nodeName = (view).nodeName
+ let nodeName = (view).nodeName || view;
let output = ["(", nodeName];
if (verbose) {
if (view instanceof TextBase) {