diff --git a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html
index fb667fe3a..9c6b4052e 100644
--- a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html
+++ b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.html
@@ -4,6 +4,6 @@
-
+
\ No newline at end of file
diff --git a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts
index 01e203e56..7b69233e6 100644
--- a/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts
+++ b/e2e/modal-navigation-ng/app/modal-second/modal-second.component.ts
@@ -1,5 +1,6 @@
import { Component } from "@angular/core";
-import { View, EventData } from "tns-core-modules/ui/core/view"
+import { View } from "tns-core-modules/ui/core/view"
+import { ActivatedRoute } from "@angular/router";
import { RouterExtensions } from "nativescript-angular/router";
@Component({
@@ -8,14 +9,14 @@ import { RouterExtensions } from "nativescript-angular/router";
templateUrl: "./modal-second.component.html"
})
export class ModalSecondComponent {
- constructor(private routerExtension: RouterExtensions) { }
+ constructor(private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) { }
- onLoaded(args: EventData) {
+ onLoaded() {
console.log("modal-second loaded");
}
goBack() {
- this.routerExtension.back();
+ this.routerExtension.back({ relativeTo: this.activeRoute });
}
close(layoutRoot: View) {
diff --git a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
index 2c9b95206..79010a093 100644
--- a/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
+++ b/e2e/modal-navigation-ng/e2e/modal-frame.e2e-spec.ts
@@ -33,8 +33,7 @@ describe("modal-frame:", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
await driver.resetApp();
await screen[root]();
}
diff --git a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
index 945cc4431..6d6846d58 100644
--- a/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
+++ b/e2e/modal-navigation-ng/e2e/modal-layout.e2e-spec.ts
@@ -33,8 +33,7 @@ describe("modal-layout:", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
await driver.resetApp();
await screen[root]();
}
diff --git a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
index 75f4867ad..b7fbbe9be 100644
--- a/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
+++ b/e2e/modal-navigation-ng/e2e/modal.shared.e2e-spec.ts
@@ -18,8 +18,7 @@ describe("Shared modal from home and back", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logPageSource(this.currentTest.title);
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
}
});
diff --git a/e2e/modal-navigation-ng/e2e/screen.ts b/e2e/modal-navigation-ng/e2e/screen.ts
index 9033d90e1..ba44fdeea 100644
--- a/e2e/modal-navigation-ng/e2e/screen.ts
+++ b/e2e/modal-navigation-ng/e2e/screen.ts
@@ -27,7 +27,7 @@ const confirmDialog = "Yes";
const confirmDialogMessage = "Message";
const closeModalNested = "Close Modal Nested";
const closeModal = "Close Modal";
-const goBack = "Go Back";
+const goBack = "Go Back(activatedRoute)";
export class Screen {
diff --git a/e2e/modal-navigation-ng/package.json b/e2e/modal-navigation-ng/package.json
index 2d1b54d97..0fd0f8b0f 100644
--- a/e2e/modal-navigation-ng/package.json
+++ b/e2e/modal-navigation-ng/package.json
@@ -57,7 +57,8 @@
"webpack": "~4.6.0",
"webpack-bundle-analyzer": "~2.13.0",
"webpack-cli": "~2.1.3",
- "webpack-sources": "~1.1.0"
+ "webpack-sources": "~1.1.0",
+ "@angular-devkit/core": "~0.7.0-beta.1"
},
"scripts": {
"e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
diff --git a/e2e/nested-router-tab-view/.gitignore b/e2e/nested-router-tab-view/.gitignore
new file mode 100644
index 000000000..dbb747daa
--- /dev/null
+++ b/e2e/nested-router-tab-view/.gitignore
@@ -0,0 +1,12 @@
+.vscode
+
+platforms
+node_modules
+hooks
+
+/**/*.js
+/**/*.map
+e2e/reports
+test-results.xml
+
+instrumentscli*.trace
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/AndroidManifest.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/AndroidManifest.xml
new file mode 100644
index 000000000..9db832151
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/AndroidManifest.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/app.gradle b/e2e/nested-router-tab-view/app/App_Resources/Android/app.gradle
new file mode 100644
index 000000000..680e43542
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/app.gradle
@@ -0,0 +1,16 @@
+// Add your native dependencies here:
+
+// Uncomment to add recyclerview-v7 dependency
+//dependencies {
+// compile 'com.android.support:recyclerview-v7:+'
+//}
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.nestedroutertabview"
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/background.png
new file mode 100644
index 000000000..eb381c258
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/icon.png
new file mode 100755
index 000000000..9cde84cd5
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/logo.png
new file mode 100644
index 000000000..5218f4c90
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-hdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/background.png
new file mode 100644
index 000000000..748b2adf5
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/icon.png
new file mode 100755
index 000000000..4d6a674b3
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/logo.png
new file mode 100644
index 000000000..b9e102a76
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-ldpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/background.png
new file mode 100644
index 000000000..efeaf2907
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/icon.png
new file mode 100755
index 000000000..92ccc85a6
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/logo.png
new file mode 100644
index 000000000..626338766
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-mdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-nodpi/splash_screen.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
new file mode 100644
index 000000000..ada77f92c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-nodpi/splash_screen.xml
@@ -0,0 +1,8 @@
+
+ -
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/background.png
new file mode 100644
index 000000000..612bbd072
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/icon.png
new file mode 100644
index 000000000..8bcde6277
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/logo.png
new file mode 100644
index 000000000..ad8ee2f4b
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xhdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/background.png
new file mode 100644
index 000000000..0fa88e235
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/icon.png
new file mode 100644
index 000000000..9d81c85dc
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/logo.png
new file mode 100644
index 000000000..668327832
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxhdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/background.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/background.png
new file mode 100644
index 000000000..c650f6438
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/background.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/icon.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/icon.png
new file mode 100644
index 000000000..9a34d0d43
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/icon.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/logo.png b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/logo.png
new file mode 100644
index 000000000..fa6331c8d
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/Android/drawable-xxxhdpi/logo.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/colors.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/colors.xml
new file mode 100644
index 000000000..a64641a9d
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/colors.xml
@@ -0,0 +1,4 @@
+
+
+ #3d5afe
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/styles.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/styles.xml
new file mode 100644
index 000000000..dac8727c8
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values-v21/styles.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values/colors.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values/colors.xml
new file mode 100644
index 000000000..74ad8829c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/Android/values/styles.xml b/e2e/nested-router-tab-view/app/App_Resources/Android/values/styles.xml
new file mode 100644
index 000000000..c793e6d4c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/Android/values/styles.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..4034b76e6
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,98 @@
+{
+ "images" : [
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "icon-29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "icon-40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "icon-60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "icon-29.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "icon-29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "icon-40.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "icon-40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "icon-76.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "icon-76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "icon-83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "icon-1024.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png
new file mode 100644
index 000000000..a1d7eb479
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
new file mode 100644
index 000000000..bb9b9e86d
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
new file mode 100644
index 000000000..ec609dcf3
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
new file mode 100644
index 000000000..a97180800
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
new file mode 100644
index 000000000..214800ee6
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
new file mode 100644
index 000000000..8554b88a8
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
new file mode 100644
index 000000000..a22626bae
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
new file mode 100644
index 000000000..a22626bae
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
new file mode 100644
index 000000000..492c9c8e8
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
new file mode 100644
index 000000000..9208113cf
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
new file mode 100644
index 000000000..24415e5a3
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
new file mode 100644
index 000000000..b3ef1bf0c
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..da4a164c9
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
new file mode 100644
index 000000000..11bfcf55c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Contents.json
@@ -0,0 +1,176 @@
+{
+ "images" : [
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "2436h",
+ "filename" : "Default-1125h.png",
+ "minimum-system-version" : "11.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "iphone",
+ "extent" : "full-screen",
+ "filename" : "Default-Landscape-X.png",
+ "minimum-system-version" : "11.0",
+ "subtype" : "2436h",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "Default-736h@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "736h",
+ "filename" : "Default-Landscape@3x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "landscape",
+ "scale" : "3x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "667h",
+ "filename" : "Default-667h@2x.png",
+ "minimum-system-version" : "8.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "extent" : "full-screen",
+ "idiom" : "iphone",
+ "subtype" : "retina4",
+ "filename" : "Default-568h@2x.png",
+ "minimum-system-version" : "7.0",
+ "orientation" : "portrait",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape@2x.png",
+ "extent" : "full-screen",
+ "minimum-system-version" : "7.0",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "iphone",
+ "filename" : "Default-568h@2x.png",
+ "extent" : "full-screen",
+ "subtype" : "retina4",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape.png",
+ "extent" : "full-screen",
+ "scale" : "1x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "portrait",
+ "idiom" : "ipad",
+ "filename" : "Default-Portrait@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "extent" : "to-status-bar",
+ "scale" : "2x"
+ },
+ {
+ "orientation" : "landscape",
+ "idiom" : "ipad",
+ "filename" : "Default-Landscape@2x.png",
+ "extent" : "full-screen",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png
new file mode 100644
index 000000000..2913f85d9
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-1125h.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png
new file mode 100644
index 000000000..d7f17fcd2
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-568h@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png
new file mode 100644
index 000000000..b88415405
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-667h@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png
new file mode 100644
index 000000000..faab4b631
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-736h@3x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png
new file mode 100644
index 000000000..cd94a3ac2
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape-X.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png
new file mode 100644
index 000000000..3365ba3cd
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png
new file mode 100644
index 000000000..a44945c1a
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png
new file mode 100644
index 000000000..e6dca6269
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Landscape@3x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png
new file mode 100644
index 000000000..1a5007962
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png
new file mode 100644
index 000000000..73d8b920f
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default-Portrait@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png
new file mode 100644
index 000000000..9f1f6ce3e
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png
new file mode 100644
index 000000000..514fc5cde
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchImage.launchimage/Default@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
new file mode 100644
index 000000000..4f4e9c506
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-AspectFill.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-AspectFill@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png
new file mode 100644
index 000000000..c293f9c7a
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png
new file mode 100644
index 000000000..233693a6e
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
new file mode 100644
index 000000000..23c0ffd7a
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json
@@ -0,0 +1,22 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-Center.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchScreen-Center@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png
new file mode 100644
index 000000000..a5a775a2b
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png
new file mode 100644
index 000000000..154c19343
Binary files /dev/null and b/e2e/nested-router-tab-view/app/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png differ
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/Info.plist b/e2e/nested-router-tab-view/app/App_Resources/iOS/Info.plist
new file mode 100644
index 000000000..ea3e3ea23
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/Info.plist
@@ -0,0 +1,47 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ ${PRODUCT_NAME}
+ CFBundleExecutable
+ ${EXECUTABLE_NAME}
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ ${PRODUCT_NAME}
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiresFullScreen
+
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+
+
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard b/e2e/nested-router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard
new file mode 100644
index 000000000..2ad9471e1
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/LaunchScreen.storyboard
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/App_Resources/iOS/build.xcconfig b/e2e/nested-router-tab-view/app/App_Resources/iOS/build.xcconfig
new file mode 100644
index 000000000..4b0118490
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/App_Resources/iOS/build.xcconfig
@@ -0,0 +1,7 @@
+// You can add custom settings here
+// for example you can uncomment the following line to force distribution code signing
+// CODE_SIGN_IDENTITY = iPhone Distribution
+// To build for device with Xcode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html
+// DEVELOPMENT_TEAM = YOUR_TEAM_ID;
+ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage;
diff --git a/e2e/nested-router-tab-view/app/app.android.css b/e2e/nested-router-tab-view/app/app.android.css
new file mode 100644
index 000000000..d69fe22a0
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.android.css
@@ -0,0 +1,35 @@
+Button{
+ font-size: 8;
+ padding-left: 5;
+ padding-right: 5;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ margin-right: 0;
+ height: 50px;
+ color: blue;
+}
+
+TextView{
+ font-size: 10;
+ margin: 0;
+ padding: 0;
+ color: green;
+}
+
+Label{
+ font-size: 10;
+ margin: 0;
+ padding: 0;
+}
+
+GridLayout{
+ margin: 0;
+ padding: 0;
+}
+
+ActionBar{
+ height: 30;
+ margin: 0;
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/app.component.html b/e2e/nested-router-tab-view/app/app.component.html
new file mode 100644
index 000000000..5aeb4e2b0
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.component.html
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/app.component.ts b/e2e/nested-router-tab-view/app/app.component.ts
new file mode 100644
index 000000000..d3e62efe5
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.component.ts
@@ -0,0 +1,26 @@
+import { Component, OnInit, AfterViewInit, AfterContentInit, ViewChild } from "@angular/core";
+import { TabViewDirective } from "nativescript-angular/directives";
+import { Router, NavigationEnd } from "@angular/router";
+import { NSLocationStrategy } from "nativescript-angular/router/ns-location-strategy";
+
+
+@Component({
+ selector: "ns-app",
+ templateUrl: "app.component.html",
+})
+
+export class AppComponent {
+ private isInitialNavigation = true;
+
+ @ViewChild(TabViewDirective) tabView: TabViewDirective;
+
+ constructor(router: Router, location: NSLocationStrategy) {
+ router.events.subscribe(e => {
+ if (e instanceof NavigationEnd) {
+ this.isInitialNavigation = false;
+ console.log("[ROUTER]: " + e.toString());
+ console.log(location.toString());
+ }
+ })
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/app.css b/e2e/nested-router-tab-view/app/app.css
new file mode 100644
index 000000000..e69de29bb
diff --git a/e2e/nested-router-tab-view/app/app.ios.css b/e2e/nested-router-tab-view/app/app.ios.css
new file mode 100644
index 000000000..38a30b36a
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.ios.css
@@ -0,0 +1,7 @@
+Button{
+ color: blue;
+}
+
+TextView{
+ color: green;
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/app.module.ngfactory.d.ts b/e2e/nested-router-tab-view/app/app.module.ngfactory.d.ts
new file mode 100644
index 000000000..793157de3
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.module.ngfactory.d.ts
@@ -0,0 +1,4 @@
+/**
+ * A dynamically generated module when compiled with AoT.
+ */
+export const AppModuleNgFactory: any;
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/app.module.ts b/e2e/nested-router-tab-view/app/app.module.ts
new file mode 100644
index 000000000..2be80e9fd
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.module.ts
@@ -0,0 +1,50 @@
+import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler, NgModuleFactoryLoader } from "@angular/core";
+import { NativeScriptModule } from "nativescript-angular/nativescript.module";
+import { AppRoutingModule, COMPONENTS, MODALCOMPONENTS } from "./app.routing";
+import { AppComponent } from "./app.component";
+
+import { DataService } from "./data.service";
+import { NSModuleFactoryLoader } from "nativescript-angular/router";
+
+import { SharedModule } from "./shared.module";
+import { enable as traceEnable, addCategories } from "tns-core-modules/trace";
+import { routerTraceCategory } from "nativescript-angular/trace";
+
+// addCategories(routerTraceCategory);
+traceEnable();
+
+class MyErrorHandler implements ErrorHandler {
+ handleError(error) {
+ console.log("### ErrorHandler Error: " + error.toString());
+ console.log("### ErrorHandler Stack: " + error.stack);
+ }
+}
+
+@NgModule({
+ bootstrap: [
+ AppComponent
+ ],
+ entryComponents: [MODALCOMPONENTS],
+ imports: [
+ SharedModule,
+ NativeScriptModule,
+ AppRoutingModule,
+ ],
+ declarations: [
+ AppComponent,
+ ...COMPONENTS,
+ MODALCOMPONENTS
+ ],
+ providers: [
+ DataService,
+ { provide: ErrorHandler, useClass: MyErrorHandler },
+ { provide: NgModuleFactoryLoader, useClass: NSModuleFactoryLoader }
+ ],
+ schemas: [
+ NO_ERRORS_SCHEMA
+ ]
+})
+/*
+Pass your application module to the bootstrapModule function located in main.ts to start your app
+*/
+export class AppModule { }
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/app.routing.ts b/e2e/nested-router-tab-view/app/app.routing.ts
new file mode 100644
index 000000000..8dd38fb0e
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/app.routing.ts
@@ -0,0 +1,64 @@
+import { NgModule } from "@angular/core";
+import { NativeScriptRouterModule } from "nativescript-angular/router";
+import { Routes } from "@angular/router";
+
+import { PlayerComponent } from "./player/players.component";
+import { PlayerDetailComponent } from "./player/player-detail.component";
+import { TeamsComponent } from "./team/teams.component";
+import { TeamDetailComponent } from "./team/team-detail.component";
+import { LoginComponent } from "./login/login.component";
+import { TabsComponent } from "./tabs/tabs.component";
+import { HomeComponent } from "./home/home.component";
+
+import { ModalComponent } from "./modal/modal.component";
+import { NestedModalComponent } from "./modal-nested/modal-nested.component";
+import { ModalSecondComponent } from "./modal-second/modal-second.component";
+import { ModalRouterComponent } from "./modal/modal-router/modal-router.component";
+
+export const COMPONENTS = [LoginComponent, TabsComponent];
+export const MODALCOMPONENTS = [ModalComponent, NestedModalComponent, ModalSecondComponent, ModalRouterComponent];
+
+const routes: Routes = [
+ { path: "", redirectTo: "/login", pathMatch: "full" },
+ //{ path: "", component: LoginComponent },
+ {
+ path: "login", component: LoginComponent
+ },
+ {
+ path: "home", component: HomeComponent, children: [
+ {
+ path: "players", component: PlayerComponent, outlet: "playerTab", children: [
+ {
+ path: "modal", outlet: "modalOutlet", component: ModalComponent, children: [
+ { path: "nested-frame-modal", component: NestedModalComponent }]
+ },
+ { path: "modal-second", outlet: "modalOutlet", component: ModalSecondComponent }
+ ]
+ },
+ { path: "player/:id", component: PlayerDetailComponent, outlet: "playerTab" },
+
+ { path: "teams", component: TeamsComponent, outlet: "teamTab" },
+ { path: "team/:id", component: TeamDetailComponent, outlet: "teamTab" },
+
+ ]
+ },
+ {
+ path: "home-lazy",
+ loadChildren: "./home-lazy/home-lazy.module#HomeLazyModule",
+ },
+ {
+ path: "tabs", component: TabsComponent, children: [
+ { path: "players", component: PlayerComponent, outlet: "playerTab" },
+ { path: "player/:id", component: PlayerDetailComponent, outlet: "playerTab" },
+
+ { path: "teams", component: TeamsComponent, outlet: "teamTab" },
+ { path: "team/:id", component: TeamDetailComponent, outlet: "teamTab" },
+ ]
+ }
+];
+
+@NgModule({
+ imports: [NativeScriptRouterModule.forRoot(routes)],
+ exports: [NativeScriptRouterModule],
+})
+export class AppRoutingModule { }
diff --git a/e2e/nested-router-tab-view/app/data.service.ts b/e2e/nested-router-tab-view/app/data.service.ts
new file mode 100644
index 000000000..8242ebd77
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/data.service.ts
@@ -0,0 +1,35 @@
+import { Injectable } from "@angular/core";
+
+export interface DataItem {
+ id: number;
+ name: string;
+}
+
+@Injectable()
+export class DataService {
+ private players = new Array(
+ { id: 1, name: "Player One" },
+ { id: 2, name: "Player Two" },
+ );
+
+ private teams = new Array(
+ { id: 1, name: "Team One" },
+ { id: 2, name: "Team Two" },
+ );
+
+ getPlayers(): DataItem[] {
+ return this.players;
+ }
+
+ getPlayer(id: number): DataItem {
+ return this.players.filter(item => item.id === id)[0];
+ }
+
+ getTeams(): DataItem[] {
+ return this.teams;
+ }
+
+ getTeam(id: number): DataItem {
+ return this.teams.filter(item => item.id === id)[0];
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/home-lazy/home-lazy.module.ts b/e2e/nested-router-tab-view/app/home-lazy/home-lazy.module.ts
new file mode 100644
index 000000000..ea82d7ea2
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/home-lazy/home-lazy.module.ts
@@ -0,0 +1,40 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { Route } from "@angular/router";
+
+import { NativeScriptCommonModule } from "nativescript-angular/common";
+import { NativeScriptRouterModule } from "nativescript-angular/router";
+import { PlayerComponent } from "../player/players.component";
+import { PlayerDetailComponent } from "../player/player-detail.component";
+import { TeamsComponent } from "../team/teams.component";
+import { TeamDetailComponent } from "../team/team-detail.component";
+import { HomeComponent } from "../home/home.component";
+import { DataService } from "../data.service";
+import { SharedModule } from "../shared.module";
+
+const routes: Route[] = [
+ {
+ path: "home",
+ component: HomeComponent,
+ children: [
+ { path: "players", component: PlayerComponent, outlet: "playerTab" },
+ { path: "player/:id", component: PlayerDetailComponent, outlet: "playerTab" },
+
+ { path: "teams", component: TeamsComponent, outlet: "teamTab" },
+ { path: "team/:id", component: TeamDetailComponent, outlet: "teamTab" },
+ ]
+ }
+];
+
+@NgModule({
+ schemas: [NO_ERRORS_SCHEMA],
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forChild(routes),
+ SharedModule
+ ],
+ providers: [
+ DataService
+ ]
+})
+export class HomeLazyModule { }
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/home/home.component.html b/e2e/nested-router-tab-view/app/home/home.component.html
new file mode 100644
index 000000000..6779e7468
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/home/home.component.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/home/home.component.ts b/e2e/nested-router-tab-view/app/home/home.component.ts
new file mode 100644
index 000000000..9ad9c71cd
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/home/home.component.ts
@@ -0,0 +1,84 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs";
+import { RouterExtensions } from "nativescript-angular/router";
+import { ActivatedRoute } from "@angular/router";
+import { confirm } from "tns-core-modules/ui/dialogs";
+
+@Component({
+ moduleId: module.id,
+ selector: "home-page",
+ templateUrl: "./home.component.html"
+})
+export class HomeComponent {
+ constructor(
+ private modal: ModalDialogService,
+ private vcRef: ViewContainerRef,
+ private activeRoute: ActivatedRoute,
+ private routerExtension: RouterExtensions) { }
+
+ ngOnInit() {
+ //this.routerExtension.navigate(["first"], { relativeTo: this.activeRoute });
+ //this.routerExtension.navigate([{ outlets: { playerTab: ["players"], teamTab: ["teams"] } }], { relativeTo: this.activeRoute });
+ //this.routerExtension.navigate(['players'], { relativeTo: this.activeRoute });
+ }
+
+ canGoBack() {
+ const canGoBack = this.routerExtension.canGoBack({ relativeTo: this.activeRoute });
+ const title = "CanGoBack(ActivatedRoute)";
+ this.onShowDialog(title, title + ` ${canGoBack}`);
+ }
+
+ canGoBackPlayers() {
+ const canGoBack = this.routerExtension.canGoBack({ outlets: ["playerTab"], relativeTo: this.activeRoute });
+ const title = "CanGoBack(Players)";
+ this.onShowDialog(title, title + ` ${canGoBack}`);
+ }
+
+ canGoBackTeams() {
+ const canGoBack = this.routerExtension.canGoBack({ outlets: ["teamTab"], relativeTo: this.activeRoute });
+ const title = "CanGoBack(Teams)";
+ this.onShowDialog(title, title + ` ${canGoBack}`);
+ }
+
+ canGoBackBoth() {
+ const canGoBack = this.routerExtension.canGoBack({ outlets: ["teamTab", "playerTab"], relativeTo: this.activeRoute });
+ const title = "CanGoBack(Both)";
+ this.onShowDialog(title, title + ` ${canGoBack}`);
+ }
+
+ backPlayers() {
+ this.routerExtension.back({ outlets: ["playerTab"], relativeTo: this.activeRoute });
+ }
+
+ backTeams() {
+ this.routerExtension.back({ outlets: ["teamTab"], relativeTo: this.activeRoute });
+ }
+
+ backBoth() {
+ this.routerExtension.back({ outlets: ["teamTab", "playerTab"], relativeTo: this.activeRoute });
+ }
+
+ backActivatedRoute() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+
+ back() {
+ this.routerExtension.back();
+ }
+
+ navigatePlayers() {
+ this.routerExtension.navigate([{ outlets: { playerTab: ['player', '1'] } }], { relativeTo: this.activeRoute, animated: true, transition: { name: "flip", duration: 2000, curve: "linear" } });
+ }
+
+ onShowDialog(title: string, result: string) {
+ let options: any = {
+ title: title,
+ message: result,
+ okButtonText: "Ok"
+ }
+
+ confirm(options).then((result: boolean) => {
+ console.log(result);
+ })
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/login/login.component.html b/e2e/nested-router-tab-view/app/login/login.component.html
new file mode 100644
index 000000000..f022099ed
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/login/login.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/login/login.component.ts b/e2e/nested-router-tab-view/app/login/login.component.ts
new file mode 100644
index 000000000..68b5916f3
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/login/login.component.ts
@@ -0,0 +1,19 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs";
+import { RouterExtensions } from "nativescript-angular/router";
+import { EventData } from "tns-core-modules/data/observable";
+import { confirm } from "ui/dialogs";
+
+import { AppModule } from "../app.module";
+
+@Component({
+ moduleId: module.id,
+ selector: "login-page",
+ templateUrl: "./login.component.html"
+})
+export class LoginComponent {
+ constructor(
+ private modal: ModalDialogService,
+ private vcRef: ViewContainerRef,
+ private routerExtension: RouterExtensions) { }
+}
diff --git a/e2e/nested-router-tab-view/app/main.aot.ts b/e2e/nested-router-tab-view/app/main.aot.ts
new file mode 100644
index 000000000..d5ff77a77
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/main.aot.ts
@@ -0,0 +1,7 @@
+// this import should be first in order to load some required settings (like globals and reflect-metadata)
+import { platformNativeScript } from "nativescript-angular/platform-static";
+
+// "./app.module.ngfactory" is a dynamically generated module when compiled with AoT.
+import { AppModuleNgFactory } from "./app.module.ngfactory";
+
+platformNativeScript().bootstrapModuleFactory(AppModuleNgFactory);
diff --git a/e2e/nested-router-tab-view/app/main.ts b/e2e/nested-router-tab-view/app/main.ts
new file mode 100644
index 000000000..a84cb8844
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/main.ts
@@ -0,0 +1,10 @@
+// this import should be first in order to load some required settings (like globals and reflect-metadata)
+import { platformNativeScriptDynamic } from "nativescript-angular/platform";
+
+import { AppModule } from "./app.module";
+
+// A traditional NativeScript application starts by initializing global objects, setting up global CSS rules, creating, and navigating to the main page.
+// Angular applications need to take care of their own initialization: modules, components, directives, routes, DI providers.
+// A NativeScript Angular app needs to make both paradigms work together, so we provide a wrapper platform object, platformNativeScriptDynamic,
+// that sets up a NativeScript application and can bootstrap the Angular framework.
+platformNativeScriptDynamic().bootstrapModule(AppModule);
diff --git a/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.html b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.html
new file mode 100644
index 000000000..13c391cb7
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.html
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.ts b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.ts
new file mode 100644
index 000000000..7069b058d
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal-nested/modal-nested.component.ts
@@ -0,0 +1,26 @@
+import { Component } from "@angular/core";
+import { View, ShownModallyData } from "ui/core/view"
+import { ModalDialogParams } from "nativescript-angular/directives/dialogs";
+
+@Component({
+ moduleId: module.id,
+ selector: "modal-nested-page",
+ templateUrl: "./modal-nested.component.html"
+})
+export class NestedModalComponent {
+ public navigationVisibility: string = "collapse";
+
+ constructor(private params: ModalDialogParams) {
+
+ console.log("ModalNestedContent.constructor: " + JSON.stringify(params));
+ this.navigationVisibility = params.context.navigationVisibility ? "visible" : "collapse";
+ }
+
+ close(layoutRoot: View) {
+ layoutRoot.closeModal();
+ }
+
+ onShowingModally(args: ShownModallyData) {
+ console.log("modal-page showingModally");
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/modal-second/modal-second.component.html b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.html
new file mode 100644
index 000000000..9c6b4052e
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/modal-second/modal-second.component.ts b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.ts
new file mode 100644
index 000000000..a1b280964
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal-second/modal-second.component.ts
@@ -0,0 +1,25 @@
+import { Component } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { View, EventData } from "ui/core/view"
+import { RouterExtensions } from "nativescript-angular/router";
+
+@Component({
+ moduleId: module.id,
+ selector: "modal-second-page",
+ templateUrl: "./modal-second.component.html"
+})
+export class ModalSecondComponent {
+ constructor(private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) { }
+
+ onLoaded() {
+ console.log("modal-second loaded");
+ }
+
+ goBack() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+
+ close(layoutRoot: View) {
+ layoutRoot.closeModal();
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.html b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.html
new file mode 100644
index 000000000..f4a99c901
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.ts b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.ts
new file mode 100644
index 000000000..f3be5f96e
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal/modal-router/modal-router.component.ts
@@ -0,0 +1,23 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { RouterExtensions } from "nativescript-angular/router";
+import { ModalDialogParams } from "nativescript-angular/directives/dialogs";
+
+@Component({
+ moduleId: module.id,
+ selector: "ns-modal-router",
+ templateUrl: "./modal-router.component.html",
+})
+
+export class ModalRouterComponent implements OnInit {
+ private modalRoute: string;
+
+ constructor(private params: ModalDialogParams, private routerExtension: RouterExtensions, private activeRoute: ActivatedRoute) {
+ this.modalRoute = params.context.modalRoute;
+ }
+
+ ngOnInit() {
+ //this.routerExtension.navigate([this.modalRoute], { relativeTo: this.activeRoute });
+ this.routerExtension.navigate([{ outlets: { modalOutlet: [ this.modalRoute] } }], { relativeTo: this.activeRoute });
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/modal/modal.component.html b/e2e/nested-router-tab-view/app/modal/modal.component.html
new file mode 100644
index 000000000..2077becc8
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal/modal.component.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/modal/modal.component.ts b/e2e/nested-router-tab-view/app/modal/modal.component.ts
new file mode 100644
index 000000000..55ff26525
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/modal/modal.component.ts
@@ -0,0 +1,86 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogParams, ModalDialogOptions, ModalDialogService } from "nativescript-angular/directives/dialogs";
+import { RouterExtensions, PageRoute } from "nativescript-angular/router";
+import { ActivatedRoute } from "@angular/router";
+import { View, ShownModallyData, EventData } from "ui/core/view"
+import { confirm } from "ui/dialogs";
+import { ModalRouterComponent } from "../modal/modal-router/modal-router.component";
+import { NestedModalComponent } from "../modal-nested/modal-nested.component";
+
+@Component({
+ moduleId: module.id,
+ selector: "modal-page",
+ templateUrl: "./modal.component.html"
+})
+export class ModalComponent {
+ public navigationVisibility: string = "collapse";
+
+ constructor(private params: ModalDialogParams,
+ private vcRef: ViewContainerRef,
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute,
+ private modal: ModalDialogService) {
+
+ console.log("\nModalContent.constructor: " + JSON.stringify(params));
+ this.navigationVisibility = params.context.navigationVisibility ? "visible" : "collapse";
+ }
+
+ close(layoutRoot: View) {
+ layoutRoot.closeModal();
+ }
+
+ ngOnInit() {
+ }
+
+ ngOnDestroy() {
+ console.log("ModalContent.ngOnDestroy");
+ }
+
+ onShowingModally(args: ShownModallyData) {
+ console.log("modal-page showingModally");
+ }
+
+ showDialogConfirm() {
+ let options = {
+ title: "Dialog",
+ message: "Message",
+ okButtonText: "Yes",
+ cancelButtonText: "No"
+ }
+
+ confirm(options).then((result: boolean) => {
+ console.log(result);
+ })
+ }
+
+ showNestedModalFrame() {
+ const options: ModalDialogOptions = {
+ context: {
+ navigationVisibility: true,
+ modalRoute: "nested-frame-modal"
+ },
+ fullscreen: true,
+ viewContainerRef: this.vcRef
+ };
+
+ this.modal.showModal(ModalRouterComponent, options).then((res: string) => {
+ console.log("nested-modal-frame closed");
+ });
+ }
+
+ showNestedModal() {
+ const options: ModalDialogOptions = {
+ context: { navigationVisibility: false },
+ fullscreen: true,
+ viewContainerRef: this.vcRef
+ };
+
+ this.modal.showModal(NestedModalComponent, options).then((res: string) => {
+ console.log("nested-modal closed");
+ });
+ }
+
+ onNavigateSecondPage() {
+ this.routerExtension.navigate(["../modal-second"], { relativeTo: this.activeRoute });
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/package.json b/e2e/nested-router-tab-view/app/package.json
new file mode 100644
index 000000000..826903ec1
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/package.json
@@ -0,0 +1,8 @@
+{
+ "android": {
+ "v8Flags": "--expose_gc"
+ },
+ "main": "main.js",
+ "name": "tns-template-hello-world-ng",
+ "version": "3.4.3"
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/player/player-detail.component.html b/e2e/nested-router-tab-view/app/player/player-detail.component.html
new file mode 100644
index 000000000..b4039955c
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/player/player-detail.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/player/player-detail.component.ts b/e2e/nested-router-tab-view/app/player/player-detail.component.ts
new file mode 100644
index 000000000..4155a1b54
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/player/player-detail.component.ts
@@ -0,0 +1,37 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { RouterExtensions } from "nativescript-angular/router";
+import { DataService, DataItem } from "../data.service";
+import { Subscription } from "rxjs";
+
+@Component({
+ selector: "ns-player-details",
+ moduleId: module.id,
+ templateUrl: "./player-detail.component.html",
+})
+export class PlayerDetailComponent implements OnInit {
+ item: DataItem;
+ subscription: Subscription;
+
+ constructor(
+ private data: DataService,
+ private route: ActivatedRoute,
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute,
+ ) { }
+
+ ngOnInit(): void {
+ this.subscription = this.route.params.subscribe(params => {
+ const id = +params["id"];
+ this.item = this.data.getPlayer(id);
+ })
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+
+ backPlayers() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/player/players.component.html b/e2e/nested-router-tab-view/app/player/players.component.html
new file mode 100644
index 000000000..780282771
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/player/players.component.html
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/player/players.component.ts b/e2e/nested-router-tab-view/app/player/players.component.ts
new file mode 100644
index 000000000..677168612
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/player/players.component.ts
@@ -0,0 +1,35 @@
+import { Component, OnInit, ViewContainerRef } from "@angular/core";
+import { DataService, DataItem } from "../data.service";
+import { RouterExtensions } from "nativescript-angular/router";
+
+import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs";
+import { ModalRouterComponent } from "../modal/modal-router/modal-router.component";
+@Component({
+ selector: "ns-players",
+ moduleId: module.id,
+ templateUrl: "./players.component.html",
+})
+export class PlayerComponent implements OnInit {
+ items: DataItem[];
+
+ constructor(private modal: ModalDialogService, private itemService: DataService, private router: RouterExtensions, private vcRef: ViewContainerRef, ) { }
+
+ ngOnInit(): void {
+ this.items = this.itemService.getPlayers();
+ }
+
+ onModalFrame() {
+ const options: ModalDialogOptions = {
+ context: {
+ navigationVisibility: true,
+ modalRoute: "modal"
+ },
+ fullscreen: true,
+ viewContainerRef: this.vcRef
+ };
+
+ this.modal.showModal(ModalRouterComponent, options).then((res: string) => {
+ console.log("moda-frame closed");
+ });
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/shared.module.ts b/e2e/nested-router-tab-view/app/shared.module.ts
new file mode 100644
index 000000000..03a4b7152
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/shared.module.ts
@@ -0,0 +1,41 @@
+import { NgModule, NO_ERRORS_SCHEMA, ErrorHandler, NgModuleFactoryLoader } from "@angular/core";
+// import { NativeScriptModule } from "nativescript-angular/nativescript.module";
+// import { AppRoutingModule, COMPONENTS } from "./app.routing";
+// import { AppComponent } from "./app.component";
+import { NativeScriptCommonModule } from "nativescript-angular/common";
+import { NativeScriptRouterModule } from "nativescript-angular/router";
+import { DataService } from "./data.service";
+
+import { HomeComponent } from "./home/home.component";
+import { PlayerComponent } from "./player/players.component";
+import { PlayerDetailComponent } from "./player/player-detail.component";
+import { TeamsComponent } from "./team/teams.component";
+import { TeamDetailComponent } from "./team/team-detail.component";
+
+@NgModule({
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule,
+ ],
+ declarations: [
+ HomeComponent,
+ PlayerComponent,
+ PlayerDetailComponent,
+ TeamsComponent,
+ TeamDetailComponent
+ ],
+ exports: [
+ HomeComponent,
+ PlayerComponent,
+ PlayerDetailComponent,
+ TeamsComponent,
+ TeamDetailComponent
+ ],
+ schemas: [
+ NO_ERRORS_SCHEMA
+ ]
+})
+/*
+Pass your application module to the bootstrapModule function located in main.ts to start your app
+*/
+export class SharedModule { }
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/tabs/tabs.component.html b/e2e/nested-router-tab-view/app/tabs/tabs.component.html
new file mode 100644
index 000000000..8bb946b66
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/tabs/tabs.component.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/tabs/tabs.component.ts b/e2e/nested-router-tab-view/app/tabs/tabs.component.ts
new file mode 100644
index 000000000..10ddc1b12
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/tabs/tabs.component.ts
@@ -0,0 +1,55 @@
+import { Component, ViewContainerRef } from "@angular/core";
+import { ModalDialogService, ModalDialogOptions } from "nativescript-angular/directives/dialogs";
+import { RouterExtensions } from "nativescript-angular/router";
+import { EventData } from "tns-core-modules/data/observable";
+import { ActivatedRoute } from "@angular/router";
+import { confirm } from "ui/dialogs";
+import { Page } from "ui/page";
+import { AppModule } from "../app.module";
+
+@Component({
+ moduleId: module.id,
+ selector: "tabs-page",
+ templateUrl: "./tabs.component.html"
+})
+export class TabsComponent {
+ constructor(
+ private modal: ModalDialogService,
+ private vcRef: ViewContainerRef,
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute,
+ private page: Page) {
+ // this.page.actionBarHidden = true;
+ }
+
+ ngOnInit() {
+ //this.routerExtension.navigate(["players"], { relativeTo: this.activeRoute });
+ this.routerExtension.navigate([{ outlets: { playerTab: ["players"], teamTab: ["teams"] } }], { relativeTo: this.activeRoute });
+ }
+
+ canGoBack() {
+ const canGoBack = this.routerExtension.canGoBack({ relativeTo: this.activeRoute });
+ const title = "CanGoBack(ActivatedRoute)";
+ this.onShowDialog(title, title + ` ${canGoBack}`);
+ }
+
+ backActivatedRoute() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+
+ back() {
+ this.routerExtension.back();
+ }
+
+ onShowDialog(title: string, result: string) {
+ let options: any = {
+ title: title,
+ message: result,
+ okButtonText: "Ok"
+ }
+
+ confirm(options).then((result: boolean) => {
+ console.log(result);
+ })
+ }
+}
diff --git a/e2e/nested-router-tab-view/app/team/team-detail.component.html b/e2e/nested-router-tab-view/app/team/team-detail.component.html
new file mode 100644
index 000000000..13f4b87b7
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/team/team-detail.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/team/team-detail.component.ts b/e2e/nested-router-tab-view/app/team/team-detail.component.ts
new file mode 100644
index 000000000..e4d92b232
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/team/team-detail.component.ts
@@ -0,0 +1,37 @@
+import { Component, OnInit } from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
+import { RouterExtensions } from "nativescript-angular/router";
+import { DataService, DataItem } from "../data.service";
+import { Subscription } from "rxjs";
+
+@Component({
+ selector: "ns-team-details",
+ moduleId: module.id,
+ templateUrl: "./team-detail.component.html",
+})
+export class TeamDetailComponent implements OnInit {
+ item: DataItem;
+ subscription: Subscription;
+
+ constructor(
+ private data: DataService,
+ private route: ActivatedRoute,
+ private routerExtension: RouterExtensions,
+ private activeRoute: ActivatedRoute,
+ ) { }
+
+ ngOnInit(): void {
+ this.subscription = this.route.params.subscribe(params => {
+ const id = +params["id"];
+ this.item = this.data.getTeam(id);
+ })
+ }
+
+ ngOnDestroy() {
+ this.subscription.unsubscribe();
+ }
+
+ backTeams() {
+ this.routerExtension.back({ relativeTo: this.activeRoute });
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/app/team/teams.component.html b/e2e/nested-router-tab-view/app/team/teams.component.html
new file mode 100644
index 000000000..4126e1d3b
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/team/teams.component.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
diff --git a/e2e/nested-router-tab-view/app/team/teams.component.ts b/e2e/nested-router-tab-view/app/team/teams.component.ts
new file mode 100644
index 000000000..119d1d2fe
--- /dev/null
+++ b/e2e/nested-router-tab-view/app/team/teams.component.ts
@@ -0,0 +1,17 @@
+import { Component, OnInit } from "@angular/core";
+import { DataService, DataItem } from "../data.service";
+
+@Component({
+ selector: "ns-teams",
+ moduleId: module.id,
+ templateUrl: "./teams.component.html",
+})
+export class TeamsComponent implements OnInit {
+ items: DataItem[];
+
+ constructor(private itemService: DataService) { }
+
+ ngOnInit(): void {
+ this.items = this.itemService.getTeams();
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts
new file mode 100644
index 000000000..5e8e88e74
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/home-tabs.e2e-spec.ts
@@ -0,0 +1,122 @@
+import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated,
+ testPlayerNextNavigated,
+ testTeamNextNavigated,
+ testPlayersNavigated,
+ canGoBack,
+ testTeamsNavigated
+} from "./shared.e2e-spec"
+
+const pages = ["Go To Home Page", "Go To Lazy Home Page"];
+
+describe("home-tabs:", () => {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async () => {
+ driver = await createDriver();
+ screen = new Screen(driver);
+ });
+
+ after(async () => {
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ pages.forEach(page => {
+ describe(`${page} home-tab:`, () => {
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("loaded home component and lists", async () => {
+ await screen.navigateToHomePage(page);
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate to Tabs without Players/Teams navigation", async () => {
+ await screen.navigateToTabsPage();
+ await screen.loadedTabs();
+ await screen.loadedPlayersList();
+ await backActivatedRoute(driver);
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate Player One/Team One then go to Tabs and back", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await screen.navigateToTabsPage();
+ await screen.loadedTabs();
+ await screen.loadedPlayersList();
+ await gotoTeamsTab(driver);
+ await screen.loadedTeamList();
+ await backActivatedRoute(driver);
+ await screen.loadedHome();
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await screen.loadedTeamDetails(screen.teamOne);
+ await backBoth(driver);
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate 2 times in Players go to Tabs and back", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testPlayerNextNavigated(screen, screen.playerTwo);
+ await screen.navigateToTabsPage();
+ await screen.loadedTabs();
+ await screen.loadedPlayersList();
+ await gotoTeamsTab(driver);
+ await screen.loadedTeamList();
+ await backActivatedRoute(driver);
+ await screen.loadedHome();
+ await screen.loadedPlayerDetails(screen.playerTwo);
+ await screen.loadedTeamList();
+ await backPlayers(driver);
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate back to Login page with back(activatedRoute)", async function () {
+ await backActivatedRoute(driver);
+ await screen.loadedLogin;
+ });
+ });
+ });
+});
+
+async function backActivatedRoute(driver: AppiumDriver) {
+ const btnBack = await driver.findElementByText("Back(ActivatedRoute)");
+ await btnBack.tap();
+}
+
+async function backPlayers(driver: AppiumDriver) {
+ const btnBackPlayers = await driver.findElementByText("Back(Players)");
+ await btnBackPlayers.tap();
+}
+
+async function backBoth(driver: AppiumDriver) {
+ const btnBackBoth = await driver.findElementByText("Back(Both)");
+ await btnBackBoth.tap();
+}
+
+async function gotoPlayersTab(driver: AppiumDriver) {
+ const btnTabPlayers = await driver.findElementByText("Players Tab");
+ await btnTabPlayers.tap();
+}
+
+async function gotoTeamsTab(driver: AppiumDriver) {
+ const btnTabTeams = await driver.findElementByText("Teams Tab");
+ await btnTabTeams.tap();
+}
diff --git a/e2e/nested-router-tab-view/e2e/screen.ts b/e2e/nested-router-tab-view/e2e/screen.ts
new file mode 100644
index 000000000..0c9126fd8
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/screen.ts
@@ -0,0 +1,145 @@
+import { AppiumDriver, SearchOptions } from "nativescript-dev-appium";
+import { assert } from "chai";
+
+const home = "Home Component";
+const login = "Login Component";
+const tabs = "Tabs Component";
+
+const playerList = "Player List";
+const playerDetails = "Player Details";
+const gotoNextPlayer = "next player";
+const gotoPlayers = "players";
+
+const teamDetails = "Team Details";
+const teamList = "Team List";
+const gotoNextTeam = "next team";
+const gotoTeams = "teams";
+
+const gotoHomePage = "Go To Home Page";
+const gotoTabsPage = "Go To Tabs Page";
+const confirmDialog = "Ok";
+
+export class Screen {
+
+ private _driver: AppiumDriver;
+
+ playerOne = "Player One";
+ playerTwo = "Player Two";
+ teamOne = "Team One";
+ teamTwo = "Team Two";
+
+ canGoBackActivatedRoute = "CanGoBack(ActivatedRoute)";
+ canGoBackPlayers = "CanGoBack(Players)";
+ canGoBackTeams = "CanGoBack(Teams)";
+ canGoBackBoth = "CanGoBack(Both)";
+
+ constructor(driver: AppiumDriver) {
+ this._driver = driver;
+ }
+
+ showDialogConfirm = async (title) => {
+ const btnShowDialogConfirm = await this._driver.findElementByText(title);
+ await btnShowDialogConfirm.tap();
+ }
+
+ loadedConfirmDialog = async (dialogMessage) => {
+ const lblDialogMessage = await this._driver.findElementByText(dialogMessage);
+ assert.isTrue(await lblDialogMessage.isDisplayed());
+ console.log(dialogMessage + " shown!");
+ }
+
+ closeDialog = async () => {
+ const btnYesDialog = await this._driver.findElementByText(confirmDialog);
+ await btnYesDialog.click();
+ }
+
+ loadedLogin = async () => {
+ const lblLogin = await this._driver.findElementByText(login);
+ assert.isTrue(await lblLogin.isDisplayed());
+ console.log(login + " loaded!");
+ }
+
+ loadedHome = async () => {
+ const lblHome = await this._driver.findElementByText(home);
+ assert.isTrue(await lblHome.isDisplayed());
+ console.log(home + " loaded!");
+ }
+
+ loadedTabs = async () => {
+ const lblTabs = await this._driver.findElementByText(tabs);
+ assert.isTrue(await lblTabs.isDisplayed());
+ console.log(tabs + " loaded!");
+ }
+
+ navigateToTabsPage = async () => {
+ const btnNavToTabsPage = await this._driver.findElementByText(gotoTabsPage);
+ await btnNavToTabsPage.tap();
+ }
+
+ loadedPlayersList = async () => {
+ const lblPlayerList = await this._driver.findElementByText(playerList);
+ assert.isTrue(await lblPlayerList.isDisplayed());
+ console.log(playerList + " loaded!");
+ }
+
+ loadedPlayerDetails = async (player) => {
+ const lblPlayerDetail = await this._driver.findElementByText(playerDetails);
+ assert.isTrue(await lblPlayerDetail.isDisplayed());
+
+ const lblPlayer = await this._driver.findElementByText(player + " Details");
+ assert.isTrue(await lblPlayer.isDisplayed());
+
+ console.log(player + " Details" + " loaded!");
+ }
+
+ loadedTeamList = async () => {
+ const lblTeamList = await this._driver.findElementByText(teamList, 10);
+ assert.isTrue(await lblTeamList.isDisplayed());
+ console.log(teamList + " loaded!");
+ }
+
+ loadedTeamDetails = async (team) => {
+ const lblTeamDetail = await this._driver.findElementByText(teamDetails);
+ assert.isTrue(await lblTeamDetail.isDisplayed());
+
+ const lblTeam = await this._driver.findElementByText(team + " Details");
+ assert.isTrue(await lblTeam.isDisplayed());
+
+ console.log(team + " Details" + " loaded!");
+ }
+
+ navigateToHomePage = async (homePageButton?) => {
+ const btnNavToHomePage = await this._driver.findElementByText(homePageButton || gotoHomePage);
+ await btnNavToHomePage.tap();
+ }
+
+ navigateToPlayer = async (player: string) => {
+ const btnNavPlayerPage = await this._driver.findElementByText(player);
+ await btnNavPlayerPage.tap();
+ }
+
+ navigateToNextPlayer = async () => {
+ const btnNavPlayerNextPage = await this._driver.findElementByText(gotoNextPlayer);
+ await btnNavPlayerNextPage.tap();
+ }
+
+ navigateToPlayers = async () => {
+ const btnNavPlayersPage = await this._driver.findElementByText(gotoPlayers);
+ await btnNavPlayersPage.tap();
+ }
+
+ navigateToTeam = async (team: string) => {
+ const btnNavTeamPage = await this._driver.findElementByText(team);
+ await btnNavTeamPage.tap();
+ }
+
+ navigateToNextTeam = async () => {
+ const btnNavTeamNextPage = await this._driver.findElementByText(gotoNextTeam);
+ await btnNavTeamNextPage.tap();
+ }
+
+ navigateToTeams = async () => {
+ const btnNavTeamsPage = await this._driver.findElementByText(gotoTeams);
+ await btnNavTeamsPage.tap();
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/e2e/setup.ts b/e2e/nested-router-tab-view/e2e/setup.ts
new file mode 100644
index 000000000..8b26e66e9
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/setup.ts
@@ -0,0 +1,9 @@
+import { startServer, stopServer } from "nativescript-dev-appium";
+
+before("start server", async () => {
+ await startServer();
+});
+
+after("stop server", async () => {
+ await stopServer();
+});
diff --git a/e2e/nested-router-tab-view/e2e/shared.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/shared.e2e-spec.ts
new file mode 100644
index 000000000..1a23b1d35
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/shared.e2e-spec.ts
@@ -0,0 +1,37 @@
+import { Screen } from "./screen"
+
+export async function canGoBack(screen: Screen, title: string, result: boolean) {
+ await screen.showDialogConfirm(title);
+ await screen.loadedConfirmDialog(title + ` ${result}`);
+ await screen.closeDialog();
+}
+
+export async function testPlayerNavigated(screen: Screen, player: string) {
+ await screen.navigateToPlayer(player);
+ await screen.loadedPlayerDetails(player);
+}
+
+export async function testPlayerNextNavigated(screen: Screen, nextPlayer: string) {
+ await screen.navigateToNextPlayer();
+ await screen.loadedPlayerDetails(nextPlayer);
+}
+
+export async function testPlayersNavigated(screen: Screen) {
+ await screen.navigateToPlayers();
+ await screen.loadedPlayersList();
+}
+
+export async function testTeamNavigated(screen: Screen, team: string) {
+ await screen.navigateToTeam(team);
+ await screen.loadedTeamDetails(team);
+}
+
+export async function testTeamNextNavigated(screen: Screen, nextTeam: string) {
+ await screen.navigateToNextTeam();
+ await screen.loadedTeamDetails(nextTeam);
+}
+
+export async function testTeamsNavigated(screen: Screen) {
+ await screen.navigateToTeams();
+ await screen.loadedTeamList();
+}
diff --git a/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts
new file mode 100644
index 000000000..5fe6eacac
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/split-view.e2e-spec.ts
@@ -0,0 +1,177 @@
+import { AppiumDriver, createDriver, SearchOptions } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated,
+ testPlayerNextNavigated,
+ testTeamNextNavigated,
+ testPlayersNavigated,
+ canGoBack
+} from "./shared.e2e-spec"
+
+const pages = ["Go To Home Page", "Go To Lazy Home Page"];
+
+describe("split-view:", () => {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async () => {
+ driver = await createDriver();
+ screen = new Screen(driver);
+ });
+
+ after(async () => {
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ pages.forEach(page => {
+ describe(`${page} split-view:`, () => {
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("loaded home component and lists", async () => {
+ await screen.navigateToHomePage(page);
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ await canGoBack(screen, screen.canGoBackActivatedRoute, true);
+ });
+
+ it("should navigate Player One/Team One then back separately", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await canGoBack(screen, screen.canGoBackPlayers, true);
+ await canGoBack(screen, screen.canGoBackTeams, true);
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ await backTeams(driver);
+ await canGoBack(screen, screen.canGoBackTeams, false);
+ await screen.loadedTeamList();
+ });
+
+ it("should navigate Player One/Team One then back separately (keep order)", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await backTeams(driver);
+ await screen.loadedTeamList();
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One/Team One then back simultaneously", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await canGoBack(screen, screen.canGoBackBoth, true);
+ await backBoth(driver);
+ await canGoBack(screen, screen.canGoBackBoth, false);
+ await screen.loadedTeamList();
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One/Team One then next Player/Team then back separately", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await testPlayerNextNavigated(screen, screen.playerTwo);
+ await testTeamNextNavigated(screen, screen.teamTwo);
+
+ await backPlayers(driver);
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await backTeams(driver);
+ await screen.loadedTeamDetails(screen.teamOne);
+ await backBoth(driver);
+ await screen.loadedTeamList();
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One/Team One then back", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await back(driver);
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await screen.loadedTeamList();
+
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One then navigate Players list then back", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testPlayerNextNavigated(screen, screen.playerTwo);
+ await testPlayersNavigated(screen);
+ await back(driver);
+ await screen.loadedPlayerDetails(screen.playerTwo);
+ await back(driver);
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await back(driver);
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One/Team One then Android back button", async () => {
+ if (driver.isAndroid) {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testTeamNavigated(screen, screen.teamOne);
+ await driver.navBack();
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await screen.loadedTeamList();
+
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ }
+ });
+
+ it("should navigate Player One then navigate Players list then Android back button", async function () {
+ if (driver.isAndroid) {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testPlayerNextNavigated(screen, screen.playerTwo);
+ await testPlayersNavigated(screen);
+ await driver.navBack();
+ await screen.loadedPlayerDetails(screen.playerTwo);
+ await driver.navBack();
+ await screen.loadedPlayersList();
+ }
+ });
+
+ it("should not navigate back when no back stack available", async function () {
+ await backTeams(driver);
+ await screen.loadedTeamList();
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate back to Login page with back(activatedRoute)", async function () {
+ await backActivatedRoute(driver);
+ await screen.loadedLogin;
+ });
+ });
+ });
+});
+
+async function backActivatedRoute(driver: AppiumDriver) {
+ const btnBack = await driver.findElementByText("Back(ActivatedRoute)");
+ await btnBack.tap();
+}
+
+async function back(driver: AppiumDriver) {
+ const btnBack = await driver.findElementByText("Back()");
+ await btnBack.tap();
+}
+
+async function backPlayers(driver: AppiumDriver) {
+ const btnBackPlayers = await driver.findElementByText("Back(Players)");
+ await btnBackPlayers.tap();
+}
+
+async function backTeams(driver: AppiumDriver) {
+ const btnBackTeams = await driver.findElementByText("Back(Teams)");
+ await btnBackTeams.tap();
+}
+
+async function backBoth(driver: AppiumDriver) {
+ const btnBackBoth = await driver.findElementByText("Back(Both)");
+ await btnBackBoth.tap();
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts
new file mode 100644
index 000000000..47b7fb0bb
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/tab-view.e2e-spec.ts
@@ -0,0 +1,96 @@
+import { AppiumDriver, createDriver } from "nativescript-dev-appium";
+import { Screen } from "./screen"
+import {
+ testPlayerNavigated,
+ testTeamNavigated,
+ testPlayerNextNavigated,
+ testTeamNextNavigated,
+} from "./shared.e2e-spec"
+
+describe("tab-view:", () => {
+ let driver: AppiumDriver;
+ let screen: Screen;
+
+ before(async () => {
+ driver = await createDriver();
+ screen = new Screen(driver);
+ });
+
+ after(async () => {
+ await driver.quit();
+ console.log("Quit driver!");
+ });
+
+ afterEach(async function () {
+ if (this.currentTest.state === "failed") {
+ await driver.logTestArtifacts(this.currentTest.title);
+ }
+ });
+
+ it("loaded home component and lists", async () => {
+ await screen.navigateToHomePage();
+ await screen.loadedHome();
+ await screen.loadedPlayersList();
+ await screen.loadedTeamList();
+ });
+
+ it("loaded tabs component, Players List and Teams List pages", async () => {
+ await screen.navigateToTabsPage();
+ await screen.loadedTabs();
+ await screen.loadedPlayersList();
+ await gotoTeamsTab(driver);
+ await screen.loadedTeamList();
+ await gotoPlayersTab(driver);
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One/Team One then back separately", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await gotoTeamsTab(driver);
+ await testTeamNavigated(screen, screen.teamOne);
+ await backTeams(driver);
+ await screen.loadedTeamList();
+ await gotoPlayersTab(driver);
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ });
+
+ it("should navigate Player One/Team One then next Player/Team then back", async () => {
+ await testPlayerNavigated(screen, screen.playerOne);
+ await testPlayerNextNavigated(screen, screen.playerTwo);
+ await gotoTeamsTab(driver);
+ await testTeamNavigated(screen, screen.teamOne);
+ await testTeamNextNavigated(screen, screen.teamTwo);
+ await gotoPlayersTab(driver);
+ await backPlayers(driver);
+ await screen.loadedPlayerDetails(screen.playerOne);
+ await gotoTeamsTab(driver);
+ await backTeams(driver);
+ await screen.loadedTeamDetails(screen.teamOne);
+ await backTeams(driver);
+ await screen.loadedTeamList();
+ await gotoPlayersTab(driver);
+ await backPlayers(driver);
+ await screen.loadedPlayersList();
+ });
+});
+
+async function gotoPlayersTab(driver: AppiumDriver) {
+ const btnTabPlayers = await driver.findElementByText("Players Tab");
+ await btnTabPlayers.tap();
+}
+
+async function gotoTeamsTab(driver: AppiumDriver) {
+ const btnTabTeams = await driver.findElementByText("Teams Tab");
+ await btnTabTeams.tap();
+}
+
+async function backTeams(driver: AppiumDriver) {
+ const btnBackTeams = await driver.findElementByText("Back-Teams");
+ await btnBackTeams.tap();
+}
+
+async function backPlayers(driver: AppiumDriver) {
+ const btnBackPlayers = await driver.findElementByText("Back-Players");
+ await btnBackPlayers.tap();
+}
diff --git a/e2e/nested-router-tab-view/e2e/tsconfig.json b/e2e/nested-router-tab-view/e2e/tsconfig.json
new file mode 100644
index 000000000..c297b2347
--- /dev/null
+++ b/e2e/nested-router-tab-view/e2e/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es6",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "importHelpers": false,
+ "types": [
+ "node",
+ "mocha",
+ "chai"
+ ],
+ "lib": [
+ "es2015",
+ "dom"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/package.json b/e2e/nested-router-tab-view/package.json
new file mode 100644
index 000000000..0c75b2cdb
--- /dev/null
+++ b/e2e/nested-router-tab-view/package.json
@@ -0,0 +1,54 @@
+{
+ "description": "NativeScript Application",
+ "license": "SEE LICENSE IN ",
+ "readme": "NativeScript Application",
+ "repository": "",
+ "nativescript": {
+ "id": "org.nativescript.nestedroutertabview",
+ "tns-ios": {
+ "version": "next"
+ },
+ "tns-android": {
+ "version": "next"
+ }
+ },
+ "dependencies": {
+ "@angular/animations": "~6.1.0",
+ "@angular/common": "~6.1.0",
+ "@angular/compiler": "~6.1.0",
+ "@angular/core": "~6.1.0",
+ "@angular/forms": "~6.1.0",
+ "@angular/http": "~6.1.0",
+ "@angular/platform-browser": "~6.1.0",
+ "@angular/platform-browser-dynamic": "~6.1.0",
+ "@angular/router": "~6.1.0",
+ "nativescript-angular": "file:../../nativescript-angular",
+ "nativescript-theme-core": "~1.0.4",
+ "reflect-metadata": "~0.1.8",
+ "rxjs": "~6.0.0-rc.1",
+ "tns-core-modules": "next",
+ "zone.js": "~0.8.2"
+ },
+ "devDependencies": {
+ "@angular/compiler-cli": "~6.1.0",
+ "@ngtools/webpack": "~6.2.0",
+ "@types/chai": "^4.0.2",
+ "@types/mocha": "^2.2.41",
+ "@types/node": "^7.0.5",
+ "babel-traverse": "6.26.0",
+ "babel-types": "6.26.0",
+ "babylon": "6.18.0",
+ "lazy": "1.0.11",
+ "mocha": "^5.2.0",
+ "mocha-junit-reporter": "^1.18.0",
+ "mocha-multi": "^1.0.1",
+ "nativescript-dev-appium": "next",
+ "nativescript-dev-typescript": "~0.7.4",
+ "nativescript-dev-webpack": "next",
+ "typescript": "~2.7.2"
+ },
+ "scripts": {
+ "e2e": "tsc -p e2e && mocha --opts ../config/mocha.opts --recursive e2e --appiumCapsLocation ../config/appium.capabilities.json",
+ "compile-tests": "tsc -p e2e --watch"
+ }
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/references.d.ts b/e2e/nested-router-tab-view/references.d.ts
new file mode 100644
index 000000000..b14f3837d
--- /dev/null
+++ b/e2e/nested-router-tab-view/references.d.ts
@@ -0,0 +1 @@
+/// Needed for autocompletion and compilation.
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/tsconfig.json b/e2e/nested-router-tab-view/tsconfig.json
new file mode 100644
index 000000000..575276e9f
--- /dev/null
+++ b/e2e/nested-router-tab-view/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "target": "es5",
+ "experimentalDecorators": true,
+ "emitDecoratorMetadata": true,
+ "noEmitHelpers": true,
+ "noEmitOnError": true,
+ "sourceMap": true,
+ "lib": [
+ "es6",
+ "dom",
+ "es2015.iterable"
+ ],
+ "baseUrl": ".",
+ "paths": {
+ "*": [
+ "./node_modules/tns-core-modules/*",
+ "./node_modules/*"
+ ],
+ "~/*": [
+ "app/*"
+ ]
+ }
+ },
+ "exclude": [
+ "node_modules",
+ "platforms"
+ ]
+}
\ No newline at end of file
diff --git a/e2e/nested-router-tab-view/tsconfig.tns.json b/e2e/nested-router-tab-view/tsconfig.tns.json
new file mode 100644
index 000000000..95f2ecee0
--- /dev/null
+++ b/e2e/nested-router-tab-view/tsconfig.tns.json
@@ -0,0 +1,7 @@
+{
+ "extends": "./tsconfig",
+ "compilerOptions": {
+ "module": "es2015",
+ "moduleResolution": "node"
+ }
+}
diff --git a/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts
index d169ff7e3..b52412004 100644
--- a/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts
+++ b/e2e/router-tab-view/e2e/tab-view-navigation.e2e-spec.ts
@@ -15,7 +15,7 @@ describe("TabView with page-router-outlet in each tab", () => {
afterEach(async function () {
if (this.currentTest.state === "failed") {
- await driver.logScreenshot(this.currentTest.title);
+ await driver.logTestArtifacts(this.currentTest.title);
}
});
diff --git a/e2e/router/app/app-routing.module.ts b/e2e/router/app/app-routing.module.ts
index dcc881a54..c4da4fec9 100644
--- a/e2e/router/app/app-routing.module.ts
+++ b/e2e/router/app/app-routing.module.ts
@@ -1,5 +1,5 @@
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
-import { NativeScriptRouterModule } from "nativescript-angular/router";
+import { NativeScriptRouterModule, NSEmptyOutletComponent } from "nativescript-angular/router";
import { FirstComponent } from "./first/first.component"
import { SecondComponent } from "./second/second.component"
@@ -17,10 +17,17 @@ export const routes = [
component: FirstComponent,
},
{
- path: "second/:depth", component: SecondComponent,
+ path: "second/:depth",
+ component: SecondComponent,
children: [
{ path: "", component: MasterComponent },
- { path: "detail/:id", component: DetailComponent }
+ { path: "detail/:id", component: DetailComponent },
+ {
+ path: "lazy-named",
+ outlet: "lazyNameOutlet",
+ component: NSEmptyOutletComponent,
+ loadChildren: "./lazy-named/lazy-named.module#LazyNamedModule",
+ }
]
},
{
@@ -50,7 +57,7 @@ export const navigatableComponents = [
];
@NgModule({
- imports: [NativeScriptRouterModule.forRoot(routes)],
+ imports: [NativeScriptRouterModule, NativeScriptRouterModule.forRoot(routes)],
exports: [NativeScriptRouterModule],
})
export class AppRoutingModule { }
diff --git a/e2e/router/app/first/first.component.ts b/e2e/router/app/first/first.component.ts
index 6c7cdcc7c..9445103e9 100644
--- a/e2e/router/app/first/first.component.ts
+++ b/e2e/router/app/first/first.component.ts
@@ -14,7 +14,6 @@ import { Page } from "tns-core-modules/ui/page";
-
diff --git a/e2e/router/app/lazy-named/lazy-named.module.ts b/e2e/router/app/lazy-named/lazy-named.module.ts
new file mode 100644
index 000000000..22cc87913
--- /dev/null
+++ b/e2e/router/app/lazy-named/lazy-named.module.ts
@@ -0,0 +1,31 @@
+import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
+import { Route } from "@angular/router";
+
+import { NativeScriptCommonModule } from "nativescript-angular/common";
+import { NativeScriptRouterModule } from "nativescript-angular/router";
+
+import { NestedMasterComponent } from "./nested-master.component"
+import { NestedDetailComponent } from "./nested-detail.component"
+
+const routes: Route[] = [
+ { path: "", component: NestedMasterComponent },
+ { path: "detail/:id", component: NestedDetailComponent }
+];
+
+@NgModule({
+ schemas: [NO_ERRORS_SCHEMA],
+ imports: [
+ NativeScriptCommonModule,
+ NativeScriptRouterModule,
+ NativeScriptRouterModule.forChild(routes)
+ ],
+ declarations: [
+ NestedMasterComponent,
+ NestedDetailComponent
+ ],
+ exports:[
+ NestedMasterComponent,
+ NestedDetailComponent
+ ]
+})
+export class LazyNamedModule { }
\ No newline at end of file
diff --git a/e2e/router/app/lazy-named/nested-detail.component.ts b/e2e/router/app/lazy-named/nested-detail.component.ts
new file mode 100644
index 000000000..07001a901
--- /dev/null
+++ b/e2e/router/app/lazy-named/nested-detail.component.ts
@@ -0,0 +1,38 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+import { ActivatedRoute, Router, Route } from "@angular/router";
+import { Location } from "@angular/common";
+import { Observable } from "rxjs";
+import { map } from "rxjs/operators";
+import { Page } from "tns-core-modules/ui/page";
+import { RouterExtensions } from "nativescript-angular/router";
+
+@Component({
+ selector: "detail",
+ template: `
+
+
+
+
+ `
+})
+export class NestedDetailComponent {
+ public id$: Observable;
+
+ constructor(private router: Router, private route: ActivatedRoute, private page: Page, private routerExt: RouterExtensions) {
+ this.page.actionBar.title = "NamedNestedDetail";
+ console.log("DetailComponent - constructor()");
+ this.id$ = route.params.pipe(map(r => r["id"]));
+ }
+
+ ngOnInit() {
+ console.log("DetailComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("DetailComponent - ngOnDestroy()");
+ }
+
+ goBack(){
+ this.routerExt.back();
+ }
+}
diff --git a/e2e/router/app/lazy-named/nested-master.component.ts b/e2e/router/app/lazy-named/nested-master.component.ts
new file mode 100644
index 000000000..2d68866f5
--- /dev/null
+++ b/e2e/router/app/lazy-named/nested-master.component.ts
@@ -0,0 +1,28 @@
+import { Component, OnInit, OnDestroy } from "@angular/core";
+import { Page } from "tns-core-modules/ui/page";
+@Component({
+ selector: "master",
+ template: `
+
+
+
+
+
+ `
+})
+export class NestedMasterComponent implements OnInit, OnDestroy {
+ public details: Array = [1, 2, 3];
+
+ constructor(private page: Page) {
+ this.page.actionBar.title = "NamedNestedMaster";
+ console.log("MasterComponent - constructor()");
+ }
+
+ ngOnInit() {
+ console.log("MasterComponent - ngOnInit()");
+ }
+
+ ngOnDestroy() {
+ console.log("MasterComponent - ngOnDestroy()");
+ }
+}
\ No newline at end of file
diff --git a/e2e/router/app/second/detail.component.ts b/e2e/router/app/second/detail.component.ts
index f7827e2d2..ffe2e67ba 100644
--- a/e2e/router/app/second/detail.component.ts
+++ b/e2e/router/app/second/detail.component.ts
@@ -1,6 +1,5 @@
-import { Component, OnInit, OnDestroy } from "@angular/core";
-import { ActivatedRoute, Router, Route } from "@angular/router";
-import { Location } from "@angular/common";
+import { Component} from "@angular/core";
+import { ActivatedRoute } from "@angular/router";
import { Page } from "tns-core-modules/ui/page";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";
@@ -18,7 +17,8 @@ import { map } from "rxjs/operators";
export class DetailComponent {
public id$: Observable;
- constructor(private router: Router, private route: ActivatedRoute) {
+ constructor(private route: ActivatedRoute, private page: Page) {
+ page.actionBarHidden = true;
console.log("DetailComponent - constructor()");
this.id$ = route.params.pipe(map(r => r["id"]));
}
diff --git a/e2e/router/app/second/master.component.ts b/e2e/router/app/second/master.component.ts
index 08605d5e5..fb3169180 100644
--- a/e2e/router/app/second/master.component.ts
+++ b/e2e/router/app/second/master.component.ts
@@ -1,19 +1,22 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
-
+import { Page } from "tns-core-modules/ui/page";
@Component({
selector: "master",
template: `
-
+
-
-
-
+
+
+
+
+
`
})
export class MasterComponent implements OnInit, OnDestroy {
public details: Array = [1, 2, 3];
- constructor() {
+ constructor(private page: Page) {
+ this.page.actionBarHidden = true;
console.log("MasterComponent - constructor()");
}
diff --git a/e2e/router/app/second/second.component.ts b/e2e/router/app/second/second.component.ts
index e01066171..08347f286 100644
--- a/e2e/router/app/second/second.component.ts
+++ b/e2e/router/app/second/second.component.ts
@@ -16,18 +16,24 @@ import { map } from "rxjs/operators";
+
-
-
-
-
+
+
+
+
+
+
+
+
+
`
})
export class SecondComponent implements OnInit, OnDestroy {
public depth$: Observable;
public nextDepth$: Observable;
- constructor(private routerExt: RouterExtensions, route: ActivatedRoute, page: Page) {
+ constructor(private routerExt: RouterExtensions, private route: ActivatedRoute, page: Page) {
console.log("SecondComponent - constructor() page: " + page);
this.depth$ = route.params.pipe(map(r => r["depth"]));
this.nextDepth$ = route.params.pipe(map(r => +r["depth"] + 1));
@@ -42,6 +48,10 @@ export class SecondComponent implements OnInit, OnDestroy {
}
goBack() {
- this.routerExt.back();
+ this.routerExt.back({ relativeTo: this.route });
+ }
+
+ loadNestedNamedOutlet() {
+ this.routerExt.navigate([{ outlets: { lazyNameOutlet: ["lazy-named"] } }], { relativeTo: this.route });
}
}
diff --git a/e2e/router/e2e/router.e2e-spec.ts b/e2e/router/e2e/router.e2e-spec.ts
index 82dcda30c..97de5f18a 100644
--- a/e2e/router/e2e/router.e2e-spec.ts
+++ b/e2e/router/e2e/router.e2e-spec.ts
@@ -190,6 +190,53 @@ describe("Nested navigation + page navigation", () => {
});
});
+describe("Nested name navigation + page navigation", () => {
+ let driver: AppiumDriver;
+
+ before(async () => {
+ driver = await createDriver();
+ await driver.resetApp();
+ });
+
+ it("should find First", async () => {
+ await assureFirstComponent(driver);
+ });
+
+ it("should navigate to Second(1)/master", async () => {
+ await findAndClick(driver, "GO TO SECOND");
+
+ await assureSecondComponent(driver, 1)
+ await assureNestedMasterComponent(driver);
+ });
+
+ it("should load nested named Master", async () => {
+ await findAndClick(driver, "LOAD NESTED NAMED OUTLET");
+ await assureNamedNestedMasterComponent(driver);
+ });
+
+ it("should navigate to nested named Master Detail/1", async () => {
+ const navigationButton =
+ await driver.findElementByText("DETAIL-NAMED 1", SearchOptions.exact);
+ navigationButton.click();
+
+ await assureNamedNestedDetailComponent(driver, 1);
+ });
+
+ it("should navigate back to Master and navigate to Detail/2", async () => {
+ let navigationButton =
+ await driver.findElementByText("BACK-NESTED", SearchOptions.exact);
+ navigationButton.click();
+
+ await assureNamedNestedMasterComponent(driver);
+
+ navigationButton =
+ await driver.findElementByText("DETAIL-NAMED 2", SearchOptions.exact);
+ navigationButton.click();
+
+ await assureNamedNestedDetailComponent(driver, 2);
+ });
+});
+
describe("Shouldn't be able to navigate back on startup", () => {
let driver: AppiumDriver;
@@ -371,6 +418,16 @@ async function assureComponentlessLazyComponent(driver: AppiumDriver) {
await driver.findElementByText("Lazy Componentless Route", SearchOptions.exact);
}
+async function assureNamedNestedMasterComponent(driver: AppiumDriver) {
+ await driver.findElementByText("NamedNestedMaster", SearchOptions.exact);
+}
+
+async function assureNamedNestedDetailComponent(driver: AppiumDriver, param: number) {
+ await driver.findElementByText("NamedNestedDetail", SearchOptions.exact);
+ await driver.findElementByText(`nested-named-param: ${param}`, SearchOptions.exact);
+
+}
+
async function assureSecondComponent(driver: AppiumDriver, param: number) {
await driver.findElementByText("SecondComponent", SearchOptions.exact);
await driver.findElementByText(`param: ${param}`, SearchOptions.exact);
diff --git a/nativescript-angular/directives/dialogs.ts b/nativescript-angular/directives/dialogs.ts
index 4fc954c96..7670320c5 100644
--- a/nativescript-angular/directives/dialogs.ts
+++ b/nativescript-angular/directives/dialogs.ts
@@ -17,6 +17,7 @@ import { AppHostView } from "../app-host-view";
import { DetachedLoader } from "../common/detached-loader";
import { PageFactory, PAGE_FACTORY } from "../platform-providers";
import { once } from "../common/utils";
+import { Frame } from "tns-core-modules/ui/frame/frame";
export interface ModalDialogOptions {
context?: any;
@@ -81,7 +82,10 @@ export class ModalDialogService {
const componentContainer = moduleRef || viewContainerRef;
const resolver = componentContainer.injector.get(ComponentFactoryResolver);
- const frame = parentView.page && parentView.page.frame;
+ let frame = parentView;
+ if (!(parentView instanceof Frame)) {
+ frame = parentView.page && parentView.page.frame;
+ }
if (frame) {
this.location._beginModalNavigation(frame);
@@ -127,10 +131,8 @@ export class ModalDialogService {
const closeCallback = once((...args) => {
doneCallback.apply(undefined, args);
if (componentView) {
- this.location._beginCloseModalNavigation();
componentView.closeModal();
- this.location.back();
- this.location._finishCloseModalNavigation();
+ this.location._closeModalNavigation();
detachedLoaderRef.instance.detectChanges();
detachedLoaderRef.destroy();
}
diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts
index e6d2ff380..0a3cf45c4 100644
--- a/nativescript-angular/platform-providers.ts
+++ b/nativescript-angular/platform-providers.ts
@@ -66,80 +66,8 @@ export const defaultPageFactoryProvider = { provide: PAGE_FACTORY, useValue: def
@Injectable()
export class FrameService {
// TODO: Add any methods that are needed to handle frame/page navigation
- private frames: { frame: Frame, name: string, rootOutlet: string }[] = [];
-
- // Return the topmost frame.
- // TabView with frames scenario: topmost() will return the root TabViewItem frame,
- // which could be the wrong topmost frame (modal with nested frame e.g.):
- // TabViewItem -> Frame -> Modal -> Frame2 -> Frame2-Navigation
getFrame(): Frame {
let topmostFrame = topmost();
- const { cachedFrame, cachedFrameRootOutlet } = this.findFrame(topmostFrame);
-
- if (cachedFrame && cachedFrameRootOutlet) {
- const topmostFrameByOutlet = this.getTopmostFrameByOutlet(cachedFrameRootOutlet);
-
- if (topmostFrameByOutlet && topmostFrameByOutlet !== cachedFrame) {
- topmostFrame = topmostFrameByOutlet;
- }
- }
-
return topmostFrame;
}
-
- addFrame(frame: Frame, name: string, rootOutlet: string) {
- this.frames.push({ frame: frame, name: name, rootOutlet: rootOutlet });
- }
-
- removeFrame(frame: Frame) {
- this.frames = this.frames.filter(currentFrame => currentFrame.frame !== frame);
- }
-
- containsOutlet(name: string) {
- let nameFound = false;
-
- for (let i = 0; i < this.frames.length; i++) {
- const currentFrame = this.frames[i];
-
- if (name && currentFrame.name === name) {
- nameFound = true;
- break;
- }
- }
-
- return nameFound;
- }
-
- findFrame(frame: Frame) {
- let cachedFrame;
- let cachedFrameRootOutlet;
-
- for (let i = 0; i < this.frames.length; i++) {
- const currentFrame = this.frames[i];
-
- if (currentFrame.frame === frame) {
- cachedFrame = currentFrame;
- cachedFrameRootOutlet = currentFrame.rootOutlet;
- break;
- }
- }
-
- return { cachedFrame, cachedFrameRootOutlet };
- }
-
- // Return the latest navigated frame from the given outlet branch.
- private getTopmostFrameByOutlet(rootOutlet: string): Frame {
- let frame: Frame;
-
- for (let i = this.frames.length - 1; i > 0; i--) {
- const currentFrame = this.frames[i];
-
- if (currentFrame.rootOutlet === rootOutlet) {
- frame = currentFrame.frame;
- break;
- }
- }
-
- return frame;
- }
}
diff --git a/nativescript-angular/router/ns-empty-outlet.component.ts b/nativescript-angular/router/ns-empty-outlet.component.ts
new file mode 100644
index 000000000..eea9a74fd
--- /dev/null
+++ b/nativescript-angular/router/ns-empty-outlet.component.ts
@@ -0,0 +1,15 @@
+import { Component } from "@angular/core";
+import { Page } from "tns-core-modules/ui/page";
+@Component({
+ // tslint:disable-next-line:component-selector
+ selector: "ns-empty-outlet",
+ moduleId: module.id,
+ template: ""
+})
+export class NSEmptyOutletComponent {
+ constructor(private page: Page) {
+ if (this.page) {
+ this.page.actionBarHidden = true;
+ }
+ }
+}
diff --git a/nativescript-angular/router/ns-location-strategy.ts b/nativescript-angular/router/ns-location-strategy.ts
index e266921c4..74f1a263b 100644
--- a/nativescript-angular/router/ns-location-strategy.ts
+++ b/nativescript-angular/router/ns-location-strategy.ts
@@ -6,6 +6,42 @@ import { NavigationTransition, Frame } from "tns-core-modules/ui/frame";
import { isPresent } from "../lang-facade";
import { FrameService } from "../platform-providers";
+export class Outlet {
+ showingModal: boolean;
+ modalNavigationDepth: number;
+ parent: Outlet;
+ isPageNavigationBack: boolean;
+
+ // More than one key available when using NSEmptyOutletComponent component
+ // in module that lazy loads children (loadChildren) and has outlet name.
+ outletKeys: Array;
+ pathByOutlets: string;
+ states: Array = [];
+ frame: Frame;
+
+ // Used in reuse-strategy by its children to determine if they should be detached too.
+ shouldDetach: boolean = true;
+ constructor(outletKey: string, pathByOutlets: string, modalNavigationDepth?: number) {
+ this.outletKeys = [outletKey];
+ this.isPageNavigationBack = false;
+ this.showingModal = false;
+ this.modalNavigationDepth = modalNavigationDepth || 0;
+ this.pathByOutlets = pathByOutlets;
+ }
+
+ peekState(): LocationState {
+ if (this.states.length > 0) {
+ return this.states[this.states.length - 1];
+ }
+ return null;
+ }
+
+ containsTopState(stateUrl: string): boolean {
+ const lastState = this.peekState();
+ return lastState && lastState.segmentGroup.toString() === stateUrl;
+ }
+}
+
export interface NavigationOptions {
clearHistory?: boolean;
animated?: boolean;
@@ -18,27 +54,21 @@ const defaultNavOptions: NavigationOptions = {
};
export interface LocationState {
- state: any;
- title: string;
- queryParams: string;
segmentGroup: UrlSegmentGroup;
isRootSegmentGroup: boolean;
isPageNavigation: boolean;
- isModalNavigation: boolean;
}
@Injectable()
export class NSLocationStrategy extends LocationStrategy {
- private statesByOutlet: { [key: string]: Array } = {};
- private currentUrlTree: UrlTree;
- private currentOutlet: string;
- private popStateCallbacks = new Array<(_: any) => any>();
+ private outlets: Array = [];
+ private currentOutlet: Outlet;
- private _isPageNavigationBack = false;
+ private popStateCallbacks = new Array<(_: any) => any>();
private _currentNavigationOptions: NavigationOptions;
+ private currentUrlTree: UrlTree;
- public _isModalClosing = false;
- public _isModalNavigation = false;
+ public _modalNavigationDepth = 0;
constructor(private frameService: FrameService) {
super();
@@ -52,19 +82,20 @@ export class NSLocationStrategy extends LocationStrategy {
return "/";
}
- const state = this.peekState(this.currentOutlet);
+ const state = this.currentOutlet && this.currentOutlet.peekState();
if (!state) {
return "/";
}
let tree = this.currentUrlTree;
+ let changedOutlet = this.getSegmentGroup(this.currentOutlet.pathByOutlets);
// Handle case where the user declares a component at path "/".
// The url serializer doesn't parse this url as having a primary outlet.
if (state.isRootSegmentGroup) {
tree.root = state.segmentGroup;
- } else {
- tree.root.children[this.currentOutlet] = state.segmentGroup;
+ } else if (changedOutlet) {
+ this.updateSegmentGroup(tree.root, changedOutlet, state.segmentGroup);
}
const urlSerializer = new DefaultUrlSerializer();
@@ -92,72 +123,63 @@ export class NSLocationStrategy extends LocationStrategy {
pushStateInternal(state: any, title: string, url: string, queryParams: string): void {
const urlSerializer = new DefaultUrlSerializer();
- const stateUrlTree: UrlTree = urlSerializer.parse(url);
- const rootOutlets = stateUrlTree.root.children;
+ this.currentUrlTree = urlSerializer.parse(url);
+ const urlTreeRoot = this.currentUrlTree.root;
+
+ if (!Object.keys(urlTreeRoot.children).length) {
+ // Handle case where the user declares a component at path "/".
+ // The url serializer doesn't parse this url as having a primary outlet.
+ const rootOutlet = this.createOutlet("primary", null, null);
+ this.currentOutlet = rootOutlet;
+ return;
+ }
- this.currentUrlTree = stateUrlTree;
+ const queue = [];
+ let currentTree = urlTreeRoot;
+
+ while (currentTree) {
+ Object.keys(currentTree.children).forEach(outletName => {
+ const currentSegmentGroup = currentTree.children[outletName];
+ currentSegmentGroup.outlet = outletName;
+ currentSegmentGroup.root = urlTreeRoot;
+
+ let outletKey = this.getSegmentGroupFullPath(currentTree) + outletName;
+ let outlet = this.findOutletByKey(outletKey);
+ const parentOutletName = currentTree.outlet || "";
+ const parentOutletKey = this.getSegmentGroupFullPath(currentTree.parent) + parentOutletName;
+ const parentOutlet = this.findOutletByKey(parentOutletKey);
+
+ const containsLastState = outlet && outlet.containsTopState(currentSegmentGroup.toString());
+ if (!outlet) {
+ // tslint:disable-next-line:max-line-length
+ outlet = this.createOutlet(outletKey, currentSegmentGroup, parentOutlet, this._modalNavigationDepth);
+ this.currentOutlet = outlet;
+ } else if (this._modalNavigationDepth > 0 && outlet.showingModal && !containsLastState) {
+ // Navigation inside modal view.
+ this.upsertModalOutlet(outlet, currentSegmentGroup);
+ } else {
+ outlet.parent = parentOutlet;
- // Handle case where the user declares a component at path "/".
- // The url serializer doesn't parse this url as having a primary outlet.
- if (!Object.keys(rootOutlets).length) {
- const outletStates = this.statesByOutlet["primary"] = this.statesByOutlet["primary"] || [];
- const isNewPage = outletStates.length === 0;
- outletStates.push({
- state: state,
- title: title,
- queryParams: queryParams,
- segmentGroup: stateUrlTree.root,
- isRootSegmentGroup: true,
- isPageNavigation: isNewPage,
- isModalNavigation: false
+ if (this.updateStates(outlet, currentSegmentGroup)) {
+ this.currentOutlet = outlet; // If states updated
+ }
+ }
+
+ queue.push(currentSegmentGroup);
});
- this.currentOutlet = "primary";
- }
-
- Object.keys(rootOutlets).forEach(outletName => {
- const outletStates = this.statesByOutlet[outletName] = this.statesByOutlet[outletName] || [];
- const isNewPage = outletStates.length === 0;
- const lastState = this.peekState(outletName);
-
- if (!lastState || lastState.segmentGroup.toString() !== rootOutlets[outletName].toString()) {
- outletStates.push({
- state: state,
- title: title,
- queryParams: queryParams,
- segmentGroup: rootOutlets[outletName],
- isRootSegmentGroup: false,
- isPageNavigation: isNewPage,
- isModalNavigation: false
- });
-
- this.currentOutlet = outletName;
- }
- });
+
+ currentTree = queue.shift();
+ }
}
replaceState(state: any, title: string, url: string, queryParams: string): void {
- const states = this.statesByOutlet[this.currentOutlet];
+ const states = this.currentOutlet && this.currentOutlet.states;
+
if (states && states.length > 0) {
if (isLogEnabled()) {
routerLog("NSLocationStrategy.replaceState changing existing state: " +
`${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
}
-
- const tree = this.currentUrlTree;
- if (url !== tree.toString()) {
- const urlSerializer = new DefaultUrlSerializer();
- const stateUrlTree: UrlTree = urlSerializer.parse(url);
- const rootOutlets = stateUrlTree.root.children;
-
- Object.keys(rootOutlets).forEach(outletName => {
- const topState = this.peekState(outletName);
-
- topState.segmentGroup = rootOutlets[outletName];
- topState.state = state;
- topState.title = title;
- topState.queryParams = queryParams;
- });
- }
} else {
if (isLogEnabled()) {
routerLog("NSLocationStrategy.replaceState pushing new state: " +
@@ -171,42 +193,11 @@ export class NSLocationStrategy extends LocationStrategy {
throw new Error("NSLocationStrategy.forward() - not implemented");
}
- back(): void {
- if (this._isModalClosing) {
- const states = this.statesByOutlet[this.currentOutlet];
- // We are closing modal view
- // clear the stack until we get to the page that opened the modal view
- let state;
- let modalStatesCleared = false;
- let count = 1;
-
- while (!modalStatesCleared) {
- state = this.peekState(this.currentOutlet);
-
- if (!state) {
- modalStatesCleared = true;
- this.callPopState(null, true);
- continue;
- }
-
- if (!state.isModalNavigation) {
- states.pop();
- count++;
- } else {
- modalStatesCleared = true;
- state.isModalNavigation = false;
- }
- }
+ back(outlet?: Outlet): void {
+ this.currentOutlet = outlet || this.currentOutlet;
- if (isLogEnabled()) {
- routerLog(`NSLocationStrategy.back() while closing modal. States popped: ${count}`);
- }
-
- if (state) {
- this.callPopState(state, true);
- }
- } else if (this._isPageNavigationBack) {
- const states = this.statesByOutlet[this.currentOutlet];
+ if (this.currentOutlet.isPageNavigationBack) {
+ const states = this.currentOutlet.states;
// We are navigating to the previous page
// clear the stack until we get to a page navigation state
let state = states.pop();
@@ -222,15 +213,19 @@ export class NSLocationStrategy extends LocationStrategy {
}
this.callPopState(state, true);
} else {
- let state = this.peekState(this.currentOutlet);
- if (state.isPageNavigation) {
+ let state = this.currentOutlet.peekState();
+ if (state && state.isPageNavigation) {
// This was a page navigation - so navigate through frame.
if (isLogEnabled()) {
routerLog("NSLocationStrategy.back() while not navigating back but top" +
" state is page - will call frame.goBack()");
}
- const frame = this.frameService.getFrame();
- frame.goBack();
+
+ if (!outlet) {
+ const topmostFrame = this.frameService.getFrame();
+ this.currentOutlet = this.getOutletByFrame(topmostFrame);
+ }
+ this.currentOutlet.frame.goBack();
} else {
// Nested navigation - just pop the state
if (isLogEnabled()) {
@@ -238,14 +233,14 @@ export class NSLocationStrategy extends LocationStrategy {
" state is not page - just pop");
}
- this.callPopState(this.statesByOutlet[this.currentOutlet].pop(), true);
+ this.callPopState(this.currentOutlet.states.pop(), true);
}
}
-
}
- canGoBack() {
- return this.statesByOutlet[this.currentOutlet].length > 1;
+ canGoBack(outlet?: Outlet) {
+ outlet = outlet || this.currentOutlet;
+ return outlet.states.length > 1;
}
onPopState(fn: (_: any) => any): void {
@@ -262,18 +257,17 @@ export class NSLocationStrategy extends LocationStrategy {
return "";
}
- private callPopState(state: LocationState, pop: boolean = true) {
+ private callPopState(state: LocationState, pop: boolean = true, outlet?: Outlet) {
+ outlet = outlet || this.currentOutlet;
const urlSerializer = new DefaultUrlSerializer();
- const rootOutlet = this.currentUrlTree.root.children[this.currentOutlet];
+ let changedOutlet = this.getSegmentGroup(outlet.pathByOutlets);
- if (state && rootOutlet) {
- this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup;
- } else {
+ if (state && changedOutlet) {
+ this.updateSegmentGroup(this.currentUrlTree.root, changedOutlet, state.segmentGroup);
+ } else if (changedOutlet) {
// when closing modal view there are scenarios (e.g. root viewContainerRef) when we need
// to clean up the named page router outlet to make sure we will open the modal properly again if needed.
- delete this.statesByOutlet[this.currentOutlet];
- delete this.currentUrlTree.root.children[this.currentOutlet];
- this.currentOutlet = Object.keys(this.statesByOutlet)[0];
+ this.updateSegmentGroup(this.currentUrlTree.root, changedOutlet, null);
}
const url = urlSerializer.serialize(this.currentUrlTree);
@@ -283,25 +277,16 @@ export class NSLocationStrategy extends LocationStrategy {
}
}
- private peekState(name: string) {
- const states = this.statesByOutlet[name] || [];
- if (states.length > 0) {
- return states[states.length - 1];
- }
- return null;
- }
-
public toString() {
let result = [];
- Object.keys(this.statesByOutlet).forEach(outletName => {
- const outletStates = this.statesByOutlet[outletName];
+ this.outlets.forEach(outlet => {
+ const outletStates = outlet.states;
const outletLog = outletStates
// tslint:disable-next-line:max-line-length
- .map((v, i) => `${outletName}.${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}].[${v.isModalNavigation ? "MODAL" : "BASE"}] "${v.segmentGroup.toString()}"`)
+ .map((v, i) => `${outlet.outletKeys}.${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}].[${outlet.modalNavigationDepth ? "MODAL" : "BASE"}] "${v.segmentGroup.toString()}"`)
.reverse();
-
result = result.concat(outletLog);
});
@@ -309,107 +294,70 @@ export class NSLocationStrategy extends LocationStrategy {
}
// Methods for syncing with page navigation in PageRouterOutlet
- public _beginBackPageNavigation(name: string, frame: Frame) {
- if (this._isPageNavigationBack) {
+ public _beginBackPageNavigation(frame: Frame) {
+ const outlet: Outlet = this.getOutletByFrame(frame);
+
+ if (!outlet || outlet.isPageNavigationBack) {
if (isLogEnabled()) {
routerError("Attempted to call startGoBack while going back.");
}
return;
}
+
if (isLogEnabled()) {
routerLog("NSLocationStrategy.startGoBack()");
}
- this._isPageNavigationBack = true;
+ outlet.isPageNavigationBack = true;
- let { cachedFrame } = this.frameService.findFrame(frame);
-
- if (cachedFrame) {
- this.currentOutlet = cachedFrame.rootOutlet;
- } else if (!this.frameService.containsOutlet(name)) {
- this.currentOutlet = name;
- }
+ this.currentOutlet = outlet;
}
- public _finishBackPageNavigation() {
- if (!this._isPageNavigationBack) {
+ public _finishBackPageNavigation(frame: Frame) {
+ const outlet: Outlet = this.getOutletByFrame(frame);
+
+ if (!outlet || !outlet.isPageNavigationBack) {
if (isLogEnabled()) {
routerError("Attempted to call endGoBack while not going back.");
}
return;
}
+
if (isLogEnabled()) {
routerLog("NSLocationStrategy.finishBackPageNavigation()");
}
- this._isPageNavigationBack = false;
- }
-
- public _isPageNavigatingBack() {
- return this._isPageNavigationBack;
+ outlet.isPageNavigationBack = false;
}
public _beginModalNavigation(frame: Frame): void {
if (isLogEnabled()) {
- routerLog("NSLocationStrategy._beginModalNavigation()");
+ routerLog("NSLocationStrategy._beginModalNavigation()");
}
- let { cachedFrameRootOutlet } = this.frameService.findFrame(frame);
-
- const lastState = this.peekState(cachedFrameRootOutlet || this.currentOutlet);
-
- if (lastState) {
- lastState.isModalNavigation = true;
- }
-
- this._isModalNavigation = true;
- }
+ this.currentOutlet = this.getOutletByFrame(frame);
+ this.currentOutlet.showingModal = true;
+ this._modalNavigationDepth++;
+ }
- public _beginCloseModalNavigation(): void {
- if (this._isModalClosing) {
- if (isLogEnabled()) {
- routerError("Attempted to call startCloseModal while closing modal.");
- }
- return;
- }
+ public _closeModalNavigation() {
if (isLogEnabled()) {
- routerLog("NSLocationStrategy.startCloseModal()");
+ routerLog("NSLocationStrategy.closeModalNavigation()");
}
- this._isModalClosing = true;
- }
+ this._modalNavigationDepth--;
- public _finishCloseModalNavigation() {
- if (!this._isModalClosing) {
- if (isLogEnabled()) {
- routerError("Attempted to call startCloseModal while not closing modal.");
- }
- return;
- }
+ this.currentOutlet = this.findOutletByModal(this._modalNavigationDepth, true) || this.currentOutlet;
+ this.currentOutlet.showingModal = false;
- if (isLogEnabled()) {
- routerLog("NSLocationStrategy.finishCloseModalNavigation()");
- }
- this._isModalNavigation = false;
- this._isModalClosing = false;
+ this.callPopState(this.currentOutlet.peekState(), false);
}
- public _beginPageNavigation(name: string, frame: Frame): NavigationOptions {
+ public _beginPageNavigation(frame: Frame): NavigationOptions {
if (isLogEnabled()) {
routerLog("NSLocationStrategy._beginPageNavigation()");
}
- let { cachedFrame } = this.frameService.findFrame(frame);
-
- if (cachedFrame) {
- this.currentOutlet = cachedFrame.rootOutlet;
- } else {
- // Changing the current outlet only if navigating in non-cached root outlet.
- if (!this.frameService.containsOutlet(name) && this.statesByOutlet[name] /* ensure root outlet exists */) {
- this.currentOutlet = name;
- }
-
- this.frameService.addFrame(frame, name, this.currentOutlet);
- }
+ this.currentOutlet = this.getOutletByFrame(frame);
+ const lastState = this.currentOutlet.peekState();
- const lastState = this.peekState(this.currentOutlet);
if (lastState) {
lastState.isPageNavigation = true;
}
@@ -419,7 +367,7 @@ export class NSLocationStrategy extends LocationStrategy {
if (isLogEnabled()) {
routerLog("NSLocationStrategy._beginPageNavigation clearing states history");
}
- this.statesByOutlet[this.currentOutlet] = [lastState];
+ this.currentOutlet.states = [lastState];
}
this._currentNavigationOptions = undefined;
@@ -432,13 +380,254 @@ export class NSLocationStrategy extends LocationStrategy {
animated: isPresent(options.animated) ? options.animated : true,
transition: options.transition
};
+
if (isLogEnabled()) {
routerLog("NSLocationStrategy._setNavigationOptions(" +
- `${JSON.stringify(this._currentNavigationOptions)})`);
+ `${JSON.stringify(this._currentNavigationOptions)})`);
}
}
- public _getStates(): { [key: string]: Array } {
- return this.statesByOutlet;
+ public _getOutlets(): Array {
+ return this.outlets;
+ }
+
+ updateOutletFrame(outlet: Outlet, frame: Frame) {
+ outlet.frame = frame;
+ this.currentOutlet = outlet;
+ }
+
+ clearOutlet(frame: Frame) {
+ this.outlets = this.outlets.filter(currentOutlet => {
+ // Remove current outlet, if it's named p-r-o, from the url tree.
+ const isEqualToCurrent = currentOutlet.pathByOutlets === this.currentOutlet.pathByOutlets;
+ const isModalOutlet = currentOutlet.modalNavigationDepth > 0;
+
+ if (currentOutlet.frame === frame && isModalOutlet && !isEqualToCurrent) {
+ this.callPopState(null, true, currentOutlet);
+ }
+ return currentOutlet.frame !== frame;
+ });
+ }
+
+ getSegmentGroupFullPath(segmentGroup: UrlSegmentGroup): string {
+ let fullPath = "";
+
+ while (segmentGroup) {
+ const url = segmentGroup.toString();
+
+ if (fullPath) {
+ fullPath = (url ? url + "/" : "") + fullPath;
+ } else {
+ fullPath = url;
+ }
+
+ segmentGroup = segmentGroup.parent;
+ }
+
+ return fullPath;
+ }
+
+ getRouteFullPath(currentRoute: any): string {
+ const outletName = currentRoute.outlet;
+ let fullPath;
+
+ currentRoute = currentRoute.parent;
+ while (currentRoute) {
+ const urls = (currentRoute.url.value || currentRoute.url);
+ let url = urls;
+
+ if (Array.isArray(urls)) {
+ url = url.join("/");
+ }
+
+ fullPath = fullPath ? (url ? url + "/" : url) + fullPath : url;
+ currentRoute = currentRoute.parent;
+ }
+
+ return fullPath ? fullPath + outletName : outletName;
+ }
+
+
+ getPathByOutlets(urlSegmentGroup: any): string {
+ if (!urlSegmentGroup) {
+ return "";
+ }
+
+ let pathToOutlet;
+ let lastPath = urlSegmentGroup.outlet || "primary";
+ let parent = urlSegmentGroup.parent;
+
+ while (parent && urlSegmentGroup.root !== parent) {
+ if (parent && parent.outlet !== lastPath) {
+ if (lastPath === "primary") {
+ lastPath = parent.outlet;
+ } else {
+ lastPath = parent.outlet;
+ pathToOutlet = lastPath + "-" + (pathToOutlet || urlSegmentGroup.outlet);
+ }
+ }
+
+ parent = parent.parent;
+ }
+
+ return pathToOutlet || lastPath;
+ }
+
+ findOutletByModal(modalNavigation: number, isShowingModal?: boolean): Outlet {
+ return this.outlets.find((outlet) => {
+ let isEqual = outlet.modalNavigationDepth === modalNavigation;
+ return isShowingModal ? isEqual && outlet.showingModal : isEqual;
+ });
+ }
+
+ findOutletByOutletPath(pathByOutlets: string): Outlet {
+ return this.outlets.find((outlet) => outlet.pathByOutlets === pathByOutlets);
+ }
+
+ findOutletByKey(outletKey: string): Outlet {
+ return this.outlets.find((outlet) => outlet.outletKeys.indexOf(outletKey) > -1);
+ }
+
+ private getOutletByFrame(frame: Frame): Outlet {
+ let outlet;
+
+ for (let index = 0; index < this.outlets.length; index++) {
+ const currentOutlet = this.outlets[index];
+
+ if (currentOutlet.frame === frame) {
+ outlet = currentOutlet;
+ break;
+ }
+ }
+
+ return outlet;
+ }
+
+ private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup): boolean {
+ const isNewPage = outlet.states.length === 0;
+ const lastState = outlet.states[outlet.states.length - 1];
+ const equalStateUrls = outlet.containsTopState(currentSegmentGroup.toString());
+
+ const locationState: LocationState = {
+ segmentGroup: currentSegmentGroup,
+ isRootSegmentGroup: false,
+ isPageNavigation: isNewPage
+ };
+
+ if (!lastState || !equalStateUrls) {
+ outlet.states.push(locationState);
+
+ if (this._modalNavigationDepth === 0 && !outlet.showingModal) {
+ this.updateParentsStates(outlet, currentSegmentGroup.parent);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private updateParentsStates(outlet: Outlet, newSegmentGroup: UrlSegmentGroup) {
+ let parentOutlet = outlet.parent;
+
+ // Update parents lastState segmentGroups
+ while (parentOutlet && newSegmentGroup) {
+ const state = parentOutlet.peekState();
+
+ if (state) {
+ state.segmentGroup = newSegmentGroup;
+ newSegmentGroup = newSegmentGroup.parent;
+ parentOutlet = parentOutlet.parent;
+ }
+ }
+ }
+
+ private createOutlet(outletKey: string, segmentGroup: any, parent: Outlet, modalNavigation?: number): Outlet {
+ let isRootSegmentGroup: boolean = false;
+
+ if (!segmentGroup) {
+ // Handle case where the user declares a component at path "/".
+ // The url serializer doesn't parse this url as having a primary outlet.
+ segmentGroup = this.currentUrlTree && this.currentUrlTree.root;
+ isRootSegmentGroup = true;
+ }
+
+ const pathByOutlets = this.getPathByOutlets(segmentGroup);
+ const newOutlet = new Outlet(outletKey, pathByOutlets, modalNavigation);
+
+ const locationState: LocationState = {
+ segmentGroup: segmentGroup,
+ isRootSegmentGroup: isRootSegmentGroup,
+ isPageNavigation: true // It is a new OutletNode.
+ };
+
+ newOutlet.states = [locationState];
+ newOutlet.parent = parent;
+ this.outlets.push(newOutlet);
+
+ return newOutlet;
+ }
+
+ private getSegmentGroup(pathByOutlets: string): UrlSegmentGroup {
+ const pathList = pathByOutlets.split("-");
+ let segmentGroup = this.currentUrlTree.root;
+
+ for (let index = 0; index < pathList.length; index++) {
+ const currentPath = pathList[index];
+ const childrenCount = Object.keys(segmentGroup.children).length;
+
+ if (childrenCount && segmentGroup.children[currentPath]) {
+ segmentGroup = segmentGroup.children[currentPath];
+ } else {
+ // If no child outlet found with the given name - forget about all previously found outlets.
+ // example: seaching for 'primary-second-primary' shouldn't return 'primary-second'
+ // if no 'primary' child available on 'second'.
+ segmentGroup = null;
+ break;
+ }
+ }
+
+ return segmentGroup;
+ }
+
+ // Traversal and replacement of segmentGroup.
+ private updateSegmentGroup(rootNode: any, oldSegmentGroup: UrlSegmentGroup, newSegmentGroup: UrlSegmentGroup) {
+ const queue = [];
+ let currentTree = rootNode;
+
+ while (currentTree) {
+ Object.keys(currentTree.children).forEach(outletName => {
+ if (currentTree.children[outletName] === oldSegmentGroup) {
+ if (newSegmentGroup) {
+ currentTree.children[outletName] = newSegmentGroup;
+ } else {
+ delete currentTree.children[outletName];
+ }
+ }
+ queue.push(currentTree.children[outletName]);
+ });
+
+ currentTree = queue.shift();
+ }
+ }
+
+ private upsertModalOutlet(parentOutlet: Outlet, segmentedGroup: UrlSegmentGroup) {
+ let currentModalOutlet = this.findOutletByModal(this._modalNavigationDepth);
+
+ // We want to treat every p-r-o as a standalone Outlet.
+ if (!currentModalOutlet) {
+ if (this._modalNavigationDepth > 1) {
+ // The parent of the current Outlet should be the previous opened modal (if any).
+ parentOutlet = this.findOutletByModal(this._modalNavigationDepth - 1);
+ }
+
+ // No currentModalOutlet available when opening 'primary' p-r-o.
+ const outletName = "primary";
+ const outletKey = parentOutlet.peekState().segmentGroup.toString() + outletName;
+ currentModalOutlet = this.createOutlet(outletKey, segmentedGroup, parentOutlet, this._modalNavigationDepth);
+ this.currentOutlet = currentModalOutlet;
+ } else if (this.updateStates(currentModalOutlet, segmentedGroup)) {
+ this.currentOutlet = currentModalOutlet; // If states updated
+ }
}
}
diff --git a/nativescript-angular/router/ns-route-reuse-strategy.ts b/nativescript-angular/router/ns-route-reuse-strategy.ts
index b1f90b791..0fa0f3e03 100644
--- a/nativescript-angular/router/ns-route-reuse-strategy.ts
+++ b/nativescript-angular/router/ns-route-reuse-strategy.ts
@@ -99,10 +99,20 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
shouldDetach(route: ActivatedRouteSnapshot): boolean {
route = findTopActivatedRouteNodeForOutlet(route);
+ const outletKey = this.location.getRouteFullPath(route);
+ const outlet = this.location.findOutletByKey(outletKey);
const key = getSnapshotKey(route);
const isPageActivated = route[pageRouterActivatedSymbol];
- const isBack = this.location._isPageNavigatingBack();
- const shouldDetach = !isBack && isPageActivated;
+ const isBack = outlet ? outlet.isPageNavigationBack : false;
+ let shouldDetach = outlet && !isBack && isPageActivated;
+
+ if (outlet) {
+ if (outlet.parent && !outlet.parent.shouldDetach) {
+ shouldDetach = false;
+ }
+
+ outlet.shouldDetach = shouldDetach;
+ }
if (isLogEnabled()) {
log(`shouldDetach isBack: ${isBack} key: ${key} result: ${shouldDetach}`);
@@ -114,19 +124,25 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
shouldAttach(route: ActivatedRouteSnapshot): boolean {
route = findTopActivatedRouteNodeForOutlet(route);
- const cache = this.cacheByOutlet[route.outlet];
+ const outletKey = this.location.getRouteFullPath(route);
+ const outlet = this.location.findOutletByKey(outletKey);
+ const cache = this.cacheByOutlet[outletKey];
if (!cache) {
return false;
}
const key = getSnapshotKey(route);
- const isBack = this.location._isPageNavigatingBack();
+ const isBack = outlet ? outlet.isPageNavigationBack : false;
const shouldAttach = isBack && cache.peek().key === key;
if (isLogEnabled()) {
log(`shouldAttach isBack: ${isBack} key: ${key} result: ${shouldAttach}`);
}
+ if (outlet) {
+ outlet.shouldDetach = true;
+ }
+
return shouldAttach;
}
@@ -138,13 +154,15 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
log(`store key: ${key}, state: ${state}`);
}
- const cache = this.cacheByOutlet[route.outlet] = this.cacheByOutlet[route.outlet] || new DetachedStateCache();
+ const outletKey = this.location.getRouteFullPath(route);
+
+ // tslint:disable-next-line:max-line-length
+ const cache = this.cacheByOutlet[outletKey] = this.cacheByOutlet[outletKey] || new DetachedStateCache();
if (state) {
let isModal = false;
- if (this.location._isModalNavigation) {
+ if (this.location._modalNavigationDepth > 0) {
isModal = true;
- this.location._isModalNavigation = false;
}
cache.push({ key, state, isModal });
@@ -152,6 +170,10 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
const topItem = cache.peek();
if (topItem.key === key) {
cache.pop();
+
+ if (!cache.length) {
+ delete this.cacheByOutlet[outletKey];
+ }
} else {
throw new Error("Trying to pop from DetachedStateCache but keys don't match. " +
`expected: ${topItem.key} actual: ${key}`);
@@ -162,13 +184,15 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
route = findTopActivatedRouteNodeForOutlet(route);
- const cache = this.cacheByOutlet[route.outlet];
+ const outletKey = this.location.getRouteFullPath(route);
+ const outlet = this.location.findOutletByKey(outletKey);
+ const cache = this.cacheByOutlet[outletKey];
if (!cache) {
return null;
}
const key = getSnapshotKey(route);
- const isBack = this.location._isPageNavigatingBack();
+ const isBack = outlet ? outlet.isPageNavigationBack : false;
const cachedItem = cache.peek();
let state = null;
@@ -199,16 +223,16 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
return shouldReuse;
}
- clearCache(outletName: string) {
- const cache = this.cacheByOutlet[outletName];
+ clearCache(outletKey: string) {
+ const cache = this.cacheByOutlet[outletKey];
if (cache) {
cache.clear();
}
}
- clearModalCache(outletName: string) {
- const cache = this.cacheByOutlet[outletName];
+ clearModalCache(outletKey: string) {
+ const cache = this.cacheByOutlet[outletKey];
if (cache) {
cache.clearModalCache();
diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts
index bccb43f70..79ca5098f 100644
--- a/nativescript-angular/router/page-router-outlet.ts
+++ b/nativescript-angular/router/page-router-outlet.ts
@@ -20,12 +20,11 @@ import { profile } from "tns-core-modules/profiling";
import { BehaviorSubject } from "rxjs";
import { DEVICE, PAGE_FACTORY, PageFactory } from "../platform-providers";
-import { routerLog as log, isLogEnabled } from "../trace";
+import { routerLog as log, routerError as error, isLogEnabled } from "../trace";
import { DetachedLoader } from "../common/detached-loader";
import { ViewUtil } from "../view-util";
-import { NSLocationStrategy } from "./ns-location-strategy";
+import { NSLocationStrategy, Outlet } from "./ns-location-strategy";
import { NSRouteReuseStrategy } from "./ns-route-reuse-strategy";
-import { FrameService } from "../platform-providers";
export class PageRoute {
activatedRoute: BehaviorSubject;
@@ -108,7 +107,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
private _activatedRoute: ActivatedRoute | null = null;
private detachedLoaderFactory: ComponentFactory;
+ private outlet: Outlet;
private name: string;
+ private isEmptyOutlet: boolean;
private viewUtil: ViewUtil;
private frame: Frame;
@@ -149,6 +150,7 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
private parentContexts: ChildrenOutletContexts,
private location: ViewContainerRef,
@Attribute("name") name: string,
+ @Attribute("isEmptyOutlet") isEmptyOutlet: boolean,
private locationStrategy: NSLocationStrategy,
private componentFactoryResolver: ComponentFactoryResolver,
private resolver: ComponentFactoryResolver,
@@ -156,9 +158,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
@Inject(DEVICE) device: Device,
@Inject(PAGE_FACTORY) private pageFactory: PageFactory,
private routeReuseStrategy: NSRouteReuseStrategy,
- elRef: ElementRef,
- private frameService: FrameService
+ elRef: ElementRef
) {
+ this.isEmptyOutlet = isEmptyOutlet;
this.frame = elRef.nativeElement;
if (isLogEnabled()) {
log(`PageRouterOutlet.constructor frame: ${this.frame}`);
@@ -174,17 +176,23 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
ngOnDestroy(): void {
// Clear accumulated modal view page cache when page-router-outlet
// destroyed on modal view closing
- this.routeReuseStrategy.clearModalCache(this.name);
this.parentContexts.onChildOutletDestroyed(this.name);
- this.frameService.removeFrame(this.frame);
+
+ if (this.outlet) {
+ this.outlet.outletKeys.forEach(key => {
+ this.routeReuseStrategy.clearModalCache(key);
+ });
+ this.locationStrategy.clearOutlet(this.frame);
+ } else {
+ log("NSLocationStrategy.ngOnDestroy: no outlet available for page-router-outlet");
+ }
}
deactivate(): void {
- if (!this.locationStrategy._isPageNavigatingBack()) {
+ if (!this.outlet.isPageNavigationBack) {
if (isLogEnabled()) {
log("Currently not in page back navigation - component should be detached instead of deactivated.");
}
-
return;
}
@@ -236,10 +244,9 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
this.activated = ref;
this._activatedRoute = activatedRoute;
-
this.markActivatedRoute(activatedRoute);
- this.locationStrategy._finishBackPageNavigation();
+ this.locationStrategy._finishBackPageNavigation(this.frame);
}
/**
@@ -251,11 +258,21 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
activatedRoute: ActivatedRoute,
resolver: ComponentFactoryResolver | null): void {
- if (this.locationStrategy._isPageNavigatingBack()) {
+ this.outlet = this.outlet || this.getOutlet(activatedRoute.snapshot);
+ if (!this.outlet) {
+ if (isLogEnabled()) {
+ error("No outlet found relative to activated route");
+ }
+ return;
+ }
+
+ this.locationStrategy.updateOutletFrame(this.outlet, this.frame);
+
+ if (this.outlet && this.outlet.isPageNavigationBack) {
if (isLogEnabled()) {
log("Currently in page back navigation - component should be reattached instead of activated.");
}
- this.locationStrategy._finishBackPageNavigation();
+ this.locationStrategy._finishBackPageNavigation(this.frame);
}
if (isLogEnabled()) {
@@ -316,17 +333,19 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
page.on(Page.navigatedFromEvent, (global).Zone.current.wrap((args: NavigatedData) => {
if (args.isBackNavigation) {
- this.locationStrategy._beginBackPageNavigation(this.name, this.frame);
+ this.locationStrategy._beginBackPageNavigation(this.frame);
this.locationStrategy.back();
}
}));
- const navOptions = this.locationStrategy._beginPageNavigation(this.name, this.frame);
+ const navOptions = this.locationStrategy._beginPageNavigation(this.frame);
// Clear refCache if navigation with clearHistory
if (navOptions.clearHistory) {
const clearCallback = () => setTimeout(() => {
- this.routeReuseStrategy.clearCache(this.name);
+ if (this.outlet) {
+ this.routeReuseStrategy.clearCache(this.outlet.outletKeys[0]);
+ }
page.off(Page.navigatedToEvent, clearCallback);
});
@@ -341,11 +360,31 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
});
}
+ // Find and mark the top activated route as an activated one.
+ // In ns-location-strategy we are reusing components only if their corresponing routes
+ // are marked as activated from this method.
private markActivatedRoute(activatedRoute: ActivatedRoute) {
- const nodeToMark = findTopActivatedRouteNodeForOutlet(activatedRoute.snapshot);
- nodeToMark[pageRouterActivatedSymbol] = true;
- if (isLogEnabled()) {
- log(`Activated route marked as page: ${routeToString(nodeToMark)}`);
+ const queue = [];
+ queue.push(activatedRoute.snapshot);
+ let currentRoute = queue.shift();
+
+ while (currentRoute) {
+ currentRoute.children.forEach(childRoute => {
+ queue.push(childRoute);
+ });
+
+ const nodeToMark = findTopActivatedRouteNodeForOutlet(currentRoute);
+ let outletKeyForRoute = this.locationStrategy.getRouteFullPath(nodeToMark);
+ let outlet = this.locationStrategy.findOutletByKey(outletKeyForRoute);
+
+ if (outlet && outlet.frame) {
+ nodeToMark[pageRouterActivatedSymbol] = true;
+ if (isLogEnabled()) {
+ log("Activated route marked as page: " + routeToString(nodeToMark));
+ }
+ }
+
+ currentRoute = queue.shift();
}
}
@@ -359,4 +398,29 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire
loadedResolver.resolveComponentFactory(component) :
this.componentFactoryResolver.resolveComponentFactory(component);
}
+
+ private getOutlet(activatedRouteSnapshot: ActivatedRouteSnapshot): Outlet {
+ const topActivatedRoute = findTopActivatedRouteNodeForOutlet(activatedRouteSnapshot);
+ const modalNavigation = this.locationStrategy._modalNavigationDepth;
+ const outletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute);
+ let outlet;
+
+ if (modalNavigation > 0) { // Modal with 'primary' p-r-o
+ outlet = this.locationStrategy.findOutletByModal(modalNavigation);
+ } else {
+ outlet = this.locationStrategy.findOutletByKey(outletKey);
+ }
+
+ // Named lazy loaded outlet.
+ if (!outlet && this.isEmptyOutlet) {
+ const parentOutletKey = this.locationStrategy.getRouteFullPath(topActivatedRoute.parent);
+ outlet = this.locationStrategy.findOutletByKey(parentOutletKey);
+
+ if (outlet) {
+ outlet.outletKeys.push(outletKey);
+ }
+ }
+
+ return outlet;
+ }
}
diff --git a/nativescript-angular/router/router-extensions.ts b/nativescript-angular/router/router-extensions.ts
index 9f12dcb67..0943f8acb 100644
--- a/nativescript-angular/router/router-extensions.ts
+++ b/nativescript-angular/router/router-extensions.ts
@@ -1,10 +1,17 @@
import { Injectable } from "@angular/core";
-import { Router, UrlTree, NavigationExtras } from "@angular/router";
-import { NSLocationStrategy, NavigationOptions } from "./ns-location-strategy";
+import { Router, UrlTree, NavigationExtras, ActivatedRoute } from "@angular/router";
+import { NSLocationStrategy, NavigationOptions, Outlet } from "./ns-location-strategy";
import { FrameService } from "../platform-providers";
+import { routerError } from "../trace";
+import { findTopActivatedRouteNodeForOutlet } from "./page-router-outlet";
export type ExtendedNavigationExtras = NavigationExtras & NavigationOptions;
+export interface BackNavigationOptions {
+ outlets?: Array;
+ relativeTo?: ActivatedRoute | null;
+}
+
@Injectable()
export class RouterExtensions {
@@ -28,12 +35,33 @@ export class RouterExtensions {
return this.router.navigateByUrl(url);
}
- public back() {
- this.locationStrategy.back();
+ public back(backNavigationOptions?: BackNavigationOptions) {
+ if (backNavigationOptions) {
+ this.backOutlets(backNavigationOptions);
+ } else {
+ this.locationStrategy.back();
+ }
}
- public canGoBack() {
- return this.locationStrategy.canGoBack();
+ public canGoBack(backNavigationOptions?: BackNavigationOptions) {
+ let canGoBack = true;
+ if (backNavigationOptions) {
+ const { outletsToBack, outlets } = this.findOutletsToBack(backNavigationOptions);
+
+ if (outletsToBack.length !== outlets.length) {
+ routerError("No outlet found relative to activated route");
+ } else {
+ outletsToBack.forEach(outletToBack => {
+ if (!this.locationStrategy.canGoBack(outletToBack)) {
+ canGoBack = false;
+ }
+ });
+ }
+ } else {
+ canGoBack = this.locationStrategy.canGoBack();
+ }
+
+ return canGoBack;
}
public backToPreviousPage() {
@@ -43,4 +71,51 @@ export class RouterExtensions {
public canGoBackToPreviousPage(): boolean {
return this.frameService.getFrame().canGoBack();
}
+
+ private backOutlets(options: BackNavigationOptions) {
+ const { outletsToBack, outlets } = this.findOutletsToBack(options);
+
+ if (outletsToBack.length !== outlets.length) {
+ routerError("No outlet found relative to activated route");
+ } else {
+ outletsToBack.forEach(outletToBack => {
+ if (outletToBack.isPageNavigationBack) {
+ routerError("Attempted to call startGoBack while going back:");
+ } else {
+ this.locationStrategy.back(outletToBack);
+ }
+ });
+ }
+ }
+
+ // tslint:disable-next-line:max-line-length
+ private findOutletsToBack(options?: BackNavigationOptions): { outletsToBack: Array, outlets: Array } {
+ const outletsToBack: Array = [];
+ const rootRoute: ActivatedRoute = this.router.routerState.root;
+ let outlets = options.outlets;
+ let relativeRoute = options.relativeTo;
+
+ if (!outlets && relativeRoute) {
+ outlets = [relativeRoute.outlet];
+ relativeRoute = relativeRoute.parent || rootRoute;
+ } else if (!relativeRoute) {
+ relativeRoute = rootRoute;
+ }
+
+ for (let index = 0; index < relativeRoute.children.length; index++) {
+ const currentRoute = relativeRoute.children[index];
+
+ if (outlets.some(currentOutlet => currentOutlet === currentRoute.outlet)) {
+ const currentRouteSnapshop = findTopActivatedRouteNodeForOutlet(currentRoute.snapshot);
+ const outletKey = this.locationStrategy.getRouteFullPath(currentRouteSnapshop);
+ let outlet = this.locationStrategy.findOutletByKey(outletKey);
+
+ if (outlet) {
+ outletsToBack.push(outlet);
+ }
+ }
+ }
+
+ return { outletsToBack: outletsToBack, outlets: outlets };
+ }
}
diff --git a/nativescript-angular/router/router.module.ts b/nativescript-angular/router/router.module.ts
index d1dad1335..385b4fe1f 100644
--- a/nativescript-angular/router/router.module.ts
+++ b/nativescript-angular/router/router.module.ts
@@ -10,14 +10,17 @@ import { NSRouteReuseStrategy } from "./ns-route-reuse-strategy";
import { RouterExtensions } from "./router-extensions";
import { NativeScriptCommonModule } from "../common";
import { FrameService } from "../platform-providers";
+import { NSEmptyOutletComponent } from "./ns-empty-outlet.component";
export { PageRoute } from "./page-router-outlet";
export { RouterExtensions } from "./router-extensions";
export { NSModuleFactoryLoader } from "./ns-module-factory-loader";
+export { NSEmptyOutletComponent } from "./ns-empty-outlet.component";
+
export type LocationState = LocationState;
@NgModule({
- declarations: [NSRouterLink, NSRouterLinkActive, PageRouterOutlet],
+ declarations: [NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent],
providers: [
{
provide: NSLocationStrategy,
@@ -32,7 +35,7 @@ export type LocationState = LocationState;
{ provide: RouteReuseStrategy, useExisting: NSRouteReuseStrategy },
],
imports: [RouterModule, NativeScriptCommonModule],
- exports: [RouterModule, NSRouterLink, NSRouterLinkActive, PageRouterOutlet],
+ exports: [RouterModule, NSRouterLink, NSRouterLinkActive, PageRouterOutlet, NSEmptyOutletComponent],
schemas: [NO_ERRORS_SCHEMA],
})
export class NativeScriptRouterModule {
diff --git a/tests/app/tests/modal-dialog.ts b/tests/app/tests/modal-dialog.ts
index 3c1e7a85f..babe84507 100644
--- a/tests/app/tests/modal-dialog.ts
+++ b/tests/app/tests/modal-dialog.ts
@@ -9,7 +9,7 @@ import { device, isIOS } from "tns-core-modules/platform";
import { ComponentFixture, async } from "@angular/core/testing";
import { nsTestBedRender, nsTestBedAfterEach, nsTestBedBeforeEach } from "nativescript-angular/testing";
-import { NSLocationStrategy } from "nativescript-angular/router/ns-location-strategy";
+import { NSLocationStrategy, Outlet } from "nativescript-angular/router/ns-location-strategy";
import { FrameService } from "nativescript-angular";
import { FakeFrameService } from "./ns-location-strategy";
const CLOSE_WAIT = isIOS ? 1000 : 0;
@@ -47,7 +47,11 @@ export class FailComponent {
`
})
export class SuccessComponent {
- constructor(public service: ModalDialogService, public vcRef: ViewContainerRef) {
+ constructor(
+ public service: ModalDialogService,
+ public vcRef: ViewContainerRef,
+ public locationStrategy: NSLocationStrategy,
+ public fakeFrameService: FrameService) {
}
}
@@ -88,6 +92,16 @@ describe("modal-dialog", () => {
nsTestBedRender(SuccessComponent)
.then((fixture: ComponentFixture) => {
const service = fixture.componentRef.instance.service;
+ const locStrategy = fixture.componentRef.instance.locationStrategy;
+ const outlet = new Outlet("primary", "primary", 0);
+
+ let parentView = fixture.componentRef.instance.vcRef.element.nativeElement;
+ parentView = parentView.page && parentView.page.frame;
+ outlet.frame = parentView;
+ locStrategy._getOutlets().push(outlet);
+
+ locStrategy.pushState(null, "test", "/test", null);
+
const comp = fixture.componentRef.instance;
return service.showModal(ModalComponent, { viewContainerRef: comp.vcRef });
})
@@ -100,6 +114,16 @@ describe("modal-dialog", () => {
nsTestBedRender(SuccessComponent)
.then((fixture: ComponentFixture) => {
const service = fixture.componentRef.instance.service;
+ const locStrategy = fixture.componentRef.instance.locationStrategy;
+ const outlet = new Outlet("primary", "primary", 0);
+
+ let parentView = fixture.componentRef.instance.vcRef.element.nativeElement;
+ parentView = parentView.page && parentView.page.frame;
+ outlet.frame = parentView;
+ locStrategy._getOutlets().push(outlet);
+
+ locStrategy.pushState(null, "test", "/test", null);
+
const comp = fixture.componentRef.instance;
return service.showModal(ModalComponent, {
viewContainerRef: comp.vcRef,
diff --git a/tests/app/tests/ns-location-strategy.ts b/tests/app/tests/ns-location-strategy.ts
index 726f5776c..c0ca05fbc 100644
--- a/tests/app/tests/ns-location-strategy.ts
+++ b/tests/app/tests/ns-location-strategy.ts
@@ -1,7 +1,7 @@
// make sure you import mocha-config before @angular/core
import { assert } from "./test-config";
-import { DefaultUrlSerializer, UrlSegmentGroup, UrlTree } from "@angular/router";
-import { NSLocationStrategy, LocationState } from "nativescript-angular/router/ns-location-strategy";
+import { DefaultUrlSerializer, UrlTree } from "@angular/router";
+import { NSLocationStrategy, LocationState, Outlet } from "nativescript-angular/router/ns-location-strategy";
import { Frame, BackstackEntry, NavigationEntry } from "tns-core-modules/ui/frame";
import { Page } from "tns-core-modules/ui/page";
import { View } from "tns-core-modules/ui/core/view";
@@ -24,6 +24,7 @@ export class FakeFrame extends View implements Frame {
currentPage: Page;
currentEntry: NavigationEntry;
animated: boolean;
+ actionBarVisibility: "always" | "auto" | "never";
transition: any;
_currentEntry: any;
@@ -43,48 +44,50 @@ export class FakeFrame extends View implements Frame {
}
public navigationQueueIsEmpty(): boolean {
- throw new Error("I am a FakeFrame");
+ throw new Error("navigationQueueIsEmpty:I am a FakeFrame");
}
public get navigationBarHeight(): number {
- throw new Error("I am a FakeFrame");
+ throw new Error("navigationBarHeight:I am a FakeFrame");
}
public _processNavigationQueue(page: Page) {
- throw new Error("I am a FakeFrame");
+ throw new Error("_processNavigationQueue:I am a FakeFrame");
}
public _updateActionBar(page?: Page) {
- throw new Error("I am a FakeFrame");
+ throw new Error("_updateActionBar:I am a FakeFrame");
}
public _getNavBarVisible(page: Page): boolean {
- throw new Error("I am a FakeFrame");
+ throw new Error("_getNavBarVisible:I am a FakeFrame");
}
public isCurrent(entry: BackstackEntry): boolean {
- throw new Error("I am a FakeFrame");
+ throw new Error("isCurrent:I am a FakeFrame");
}
setCurrent(entry: BackstackEntry, isBack: boolean): void {
- throw new Error("I am a FakeFrame");
+ throw new Error("setCurrent:I am a FakeFrame");
}
_findEntryForTag(fragmentTag: string): BackstackEntry {
- throw new Error("I am a FakeFrame");
+ throw new Error("_findEntryForTag:I am a FakeFrame");
}
_updateBackstack(entry: BackstackEntry, isBack: boolean): void {
- throw new Error("I am a FakeFrame");
+ throw new Error("_updateBackstack:I am a FakeFrame");
}
_pushInFrameStack() {
- throw new Error("I am a FakeFrame");
+ throw new Error("_pushInFrameStack:I am a FakeFrame");
}
_removeFromFrameStack() {
- throw new Error("I am a FakeFrame");
+ throw new Error("_removeFromFrameStack:I am a FakeFrame");
}
}
-function initStrategy(initUrl: string, back?: () => void): NSLocationStrategy {
- const strategy = new NSLocationStrategy(new FakeFrameService(back));
+// tslint:disable-next-line:max-line-length
+function initStrategy(initUrl: string, back?: () => void): { strategy: NSLocationStrategy, frameService: FrameService } {
+ const frameService = new FakeFrameService(back);
+ const strategy = new NSLocationStrategy(frameService);
strategy.pushState(null, null, initUrl, null); // load initial state
- return strategy;
+ return { strategy: strategy, frameService: frameService };
}
function assertStatesEqual(actual: Array, expected: Array) {
@@ -95,6 +98,7 @@ function assertStatesEqual(actual: Array, expected: Array, expected: Array {
@@ -155,33 +159,33 @@ describe("NSLocationStrategy", () => {
});
it("pushState changes path", () => {
- const strategy = initStrategy("/");
+ const { strategy } = initStrategy("/");
strategy.pushState(null, "test", "/test", null);
assert.equal(strategy.path(), "/test");
});
it("pushState changes path with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
strategy.pushState(null, "test", "/(test1:test12//test2:test2)", null);
assert.equal(strategy.path(), "/(test1:test12//test2:test2)");
});
it("canGoBack() return false initially", () => {
- const strategy = initStrategy("/");
+ const { strategy } = initStrategy("/");
assert.isFalse(strategy.canGoBack(), "canGoBack() should return false if there are no navigations");
});
it("canGoBack() return false initially with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
assert.isFalse(strategy.canGoBack(), "canGoBack() should return false if there are no navigations");
});
it("canGoBack() return true after navigation", () => {
- const strategy = initStrategy("/");
+ const { strategy } = initStrategy("/");
strategy.pushState(null, "test", "/test", null);
@@ -189,7 +193,7 @@ describe("NSLocationStrategy", () => {
});
it("canGoBack() return true after navigation with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
strategy.pushState(null, "test", "/(test1:test12//test2:test2)", null);
@@ -197,7 +201,7 @@ describe("NSLocationStrategy", () => {
});
it("back() calls onPopState", () => {
- const strategy = initStrategy("/");
+ const { strategy } = initStrategy("/");
let popCount = 0;
strategy.onPopState(() => {
popCount++;
@@ -213,7 +217,7 @@ describe("NSLocationStrategy", () => {
});
it("back() calls onPopState with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
let popCount = 0;
strategy.onPopState(() => {
popCount++;
@@ -229,7 +233,7 @@ describe("NSLocationStrategy", () => {
});
it("replaceState() replaces state - doesn't call onPopState", () => {
- const strategy = initStrategy("/");
+ const { strategy } = initStrategy("/");
let popCount = 0;
strategy.onPopState(() => {
popCount++;
@@ -239,13 +243,15 @@ describe("NSLocationStrategy", () => {
assert.equal(strategy.path(), "/test");
strategy.replaceState(null, "test2", "/test2", null);
- assert.equal(strategy.path(), "/test2");
+ // Currently replaceState does nothing since this shouldn't affect any functionality on {N} side.
+ // replaceState should be relevant only in Web.
+ // assert.equal(strategy.path(), "/test2");
assert.equal(popCount, 0); // no onPopState when replacing
});
it("replaceState() replaces state - doesn't call onPopState with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
let popCount = 0;
strategy.onPopState(() => {
popCount++;
@@ -255,58 +261,78 @@ describe("NSLocationStrategy", () => {
assert.equal(strategy.path(), "/(test1:test12//test2:test2)");
strategy.replaceState(null, "test2", "/(test1:test13//test2:test2)", null);
- assert.equal(strategy.path(), "/(test1:test13//test2:test2)");
+ // Currently replaceState does nothing since this shouldn't affect any functionality on {N} side.
+ // replaceState should be relevant only in Web.
+ // assert.equal(strategy.path(), "/(test1:test13//test2:test2)");
assert.equal(popCount, 0); // no onPopState when replacing
});
it("pushState() with page navigation", () => {
- const strategy = initStrategy("/");
- const expectedStates: Array = [createState("/", "primary", true, false, true)];
+ const { strategy } = initStrategy("/");
+ const outletName = "primary";
+ const expectedStates: Array = [createState("/", outletName, true, true)];
+ const frame = new FakeFrame();
- simulatePageNavigation(strategy, "/page", "primary");
- expectedStates.push(createState("/page", "primary", true));
+ simulatePageNavigation(strategy, "/page", frame, outletName);
+ expectedStates.push(createState("/page", outletName, true));
strategy.pushState(null, null, "/internal", null);
- expectedStates.push(createState("/internal", "primary"));
+ expectedStates.push(createState("/internal", outletName));
+
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
- assertStatesEqual(strategy._getStates()["primary"], expectedStates);
+ assertStatesEqual(outlet.states, expectedStates);
});
it("pushState() with page navigation with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
+ const frame = new FakeFrame();
+ const frame2 = new FakeFrame();
+ const outletName = "test1";
+ const outletName2 = "test2";
const expectedStatesTest1: Array = [
- createState("/(test1:test1//test2:test2)", "test1", true)
+ createState("/(test1:test1//test2:test2)", outletName, true)
];
const expectedStatesTest2: Array = [
- createState("/(test1:test1//test2:test2)", "test2", true)
+ createState("/(test1:test1//test2:test2)", outletName2, true)
];
- simulatePageNavigation(strategy, "/(test1:page//test2:test2)", "test1");
- expectedStatesTest1.push(createState("/(test1:page//test2:test2)", "test1", true));
+ simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame, outletName);
+ simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame2, outletName2);
+ expectedStatesTest1.push(createState("/(test1:page//test2:test2)", outletName, true));
strategy.pushState(null, null, "/(test1:internal//test2:test2)", null);
- expectedStatesTest1.push(createState("/(test1:internal//test2:test2)", "test1"));
+ expectedStatesTest1.push(createState("/(test1:internal//test2:test2)", outletName));
- assertStatesEqual(strategy._getStates()["test1"], expectedStatesTest1);
- assertStatesEqual(strategy._getStates()["test2"], expectedStatesTest2);
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
+ const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2);
+
+ assertStatesEqual(outlet.states, expectedStatesTest1);
+ assertStatesEqual(outlet2.states, expectedStatesTest2);
});
it("back() when on page-state calls frame.goBack() if no page navigation in progress", () => {
let frameBackCount = 0;
- const strategy = initStrategy("/", () => {
+ const outletName = "primary";
+
+ const { strategy, frameService } = initStrategy("/", () => {
frameBackCount++;
});
+
+ const currentFrame = frameService.getFrame();
+
let popCount = 0;
strategy.onPopState(() => {
popCount++;
});
- simulatePageNavigation(strategy, "/page", "primary");
+ simulatePageNavigation(strategy, "/page", currentFrame, outletName);
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
assert.equal(frameBackCount, 0);
assert.equal(popCount, 0);
- assert.equal(strategy._getStates()["primary"].length, 2);
+ assert.equal(outlet.states.length, 2);
// Act
strategy.back();
@@ -314,12 +340,15 @@ describe("NSLocationStrategy", () => {
// Assert
assert.equal(frameBackCount, 1);
assert.equal(popCount, 0);
- assert.equal(strategy._getStates()["primary"].length, 2);
+ assert.equal(outlet.states.length, 2);
});
it("back() when on page-state calls frame.goBack() if no page navigation in progress with named outlets", () => {
let frameBackCount = 0;
- const strategy = initStrategy("/(test1:test1//test2:test2)", () => {
+ const frame = new FakeFrame();
+ const outletName = "test1";
+ const outletName2 = "test2";
+ const { strategy, frameService } = initStrategy("/(test1:test1//test2:test2)", () => {
frameBackCount++;
});
let popCount = 0;
@@ -327,12 +356,16 @@ describe("NSLocationStrategy", () => {
popCount++;
});
- simulatePageNavigation(strategy, "/(test1:page//test2:test2)", "test1");
+ const currentFrame = frameService.getFrame();
+ simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame, outletName);
+ simulatePageNavigation(strategy, "/(test1:page//test2:test2)", currentFrame, outletName2);
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
+ const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2);
assert.equal(frameBackCount, 0);
assert.equal(popCount, 0);
- assert.equal(strategy._getStates()["test1"].length, 2);
- assert.equal(strategy._getStates()["test2"].length, 1);
+ assert.equal(outlet.states.length, 2);
+ assert.equal(outlet2.states.length, 1);
// Act
strategy.back();
@@ -340,38 +373,47 @@ describe("NSLocationStrategy", () => {
// Assert
assert.equal(frameBackCount, 1);
assert.equal(popCount, 0);
- assert.equal(strategy._getStates()["test1"].length, 2);
- assert.equal(strategy._getStates()["test2"].length, 1);
+ assert.equal(outlet.states.length, 2);
+ assert.equal(outlet2.states.length, 1);
});
it("back() when on page-state navigates back if page navigation is in progress", () => {
let frameBackCount = 0;
- const strategy = initStrategy("/", () => {
+ const outletName = "primary";
+ const { strategy, frameService } = initStrategy("/", () => {
frameBackCount++;
});
+
+ const currentFrame = frameService.getFrame();
let popCount = 0;
strategy.onPopState(() => {
popCount++;
});
- simulatePageNavigation(strategy, "/page", "primary");
+ simulatePageNavigation(strategy, "/page", currentFrame, outletName);
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
assert.equal(frameBackCount, 0);
assert.equal(popCount, 0);
- assert.equal(strategy._getStates()["primary"].length, 2);
+ assert.equal(outlet.states.length, 2);
// Act
- simulatePageBack(strategy, "primary");
+ simulatePageBack(strategy, currentFrame);
// Assert
assert.equal(frameBackCount, 0);
assert.equal(popCount, 1);
- assert.equal(strategy._getStates()["primary"].length, 1);
+ assert.equal(outlet.states.length, 1);
});
it("back() when on page-state navigates back if page navigation is in progress with named outlets", () => {
let frameBackCount = 0;
- const strategy = initStrategy("/(test1:test1//test2:test2)", () => {
+ const frame = new FakeFrame();
+ const frame2 = new FakeFrame();
+ const outletName = "test1";
+ const outletName2 = "test2";
+
+ const { strategy, frameService } = initStrategy("/(test1:test1//test2:test2)", () => {
frameBackCount++;
});
let popCount = 0;
@@ -379,49 +421,61 @@ describe("NSLocationStrategy", () => {
popCount++;
});
- simulatePageNavigation(strategy, "/(test1:page//test2:test2)", "test1");
+ simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame, outletName);
+ simulatePageNavigation(strategy, "/(test1:page//test2:test2)", frame2, outletName2);
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
+ const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2);
assert.equal(frameBackCount, 0);
assert.equal(popCount, 0);
- assert.equal(strategy._getStates()["test1"].length, 2);
- assert.equal(strategy._getStates()["test2"].length, 1);
+ assert.equal(outlet.states.length, 2);
+ assert.equal(outlet2.states.length, 1);
// Act
- simulatePageBack(strategy, "test1");
+ simulatePageBack(strategy, frame);
// Assert
assert.equal(frameBackCount, 0);
assert.equal(popCount, 1);
- assert.equal(strategy._getStates()["test1"].length, 1);
- assert.equal(strategy._getStates()["test2"].length, 1);
+ assert.equal(outlet.states.length, 1);
+ assert.equal(outlet2.states.length, 1);
});
it("pushState() with clearHistory clears history", () => {
- const strategy = initStrategy("/");
-
+ const { strategy } = initStrategy("/");
+ const frame = new FakeFrame();
+ const outletName = "primary";
// Act
strategy._setNavigationOptions({ clearHistory: true });
- simulatePageNavigation(strategy, "/cleared", "primary");
-
+ simulatePageNavigation(strategy, "/cleared", frame, outletName);
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
// Assert
- assertStatesEqual(strategy._getStates()["primary"], [createState("/cleared", "primary", true)]);
+ assertStatesEqual(outlet.states, [createState("/cleared", outletName, true)]);
});
it("pushState() with clearHistory clears history with named outlets", () => {
- const strategy = initStrategy("/(test1:test1//test2:test2)");
+ const { strategy } = initStrategy("/(test1:test1//test2:test2)");
+ const frame = new FakeFrame();
+ const outletName = "test1";
+ const frame2 = new FakeFrame();
+ const outletName2 = "test2";
// Act
strategy._setNavigationOptions({ clearHistory: true });
- simulatePageNavigation(strategy, "/(test1:cleared//test2:test2)", "test1");
+ simulatePageNavigation(strategy, "/(test1:cleared//test2:test2)", frame, outletName);
+ simulatePageNavigation(strategy, "/(test1:cleared//test2:test2)", frame2, outletName2);
const expectedStatesTest1: Array = [
- createState("/(test1:cleared//test2:test2)", "test1", true)
+ createState("/(test1:cleared//test2:test2)", outletName, true)
];
const expectedStatesTest2: Array = [
- createState("/(test1:cleared//test2:test2)", "test2", true)
+ createState("/(test1:cleared//test2:test2)", outletName2, true)
];
+ const outlet: Outlet = strategy.findOutletByOutletPath(outletName);
+ const outlet2: Outlet = strategy.findOutletByOutletPath(outletName2);
+
// Assert
- assertStatesEqual(strategy._getStates()["test1"], expectedStatesTest1);
- assertStatesEqual(strategy._getStates()["test2"], expectedStatesTest2);
+ assertStatesEqual(outlet.states, expectedStatesTest1);
+ assertStatesEqual(outlet2.states, expectedStatesTest2);
});
});