From dc70da7629b8faf0d897387c393eca3280fe187e Mon Sep 17 00:00:00 2001 From: vakrilov Date: Thu, 10 Nov 2016 15:02:15 +0200 Subject: [PATCH 1/5] Optimize platfroms for AOT --- nativescript-angular/dom-adapter.ts | 6 - nativescript-angular/index.ts | 1 + nativescript-angular/nativescript.module.ts | 5 +- nativescript-angular/platform-common.ts | 194 +++++++++++++++++ nativescript-angular/platform-static.ts | 17 ++ nativescript-angular/platform.ts | 198 +----------------- .../router/ns-router-link-active.ts | 2 +- .../router/page-router-outlet.ts | 2 +- 8 files changed, 222 insertions(+), 203 deletions(-) create mode 100644 nativescript-angular/platform-common.ts create mode 100644 nativescript-angular/platform-static.ts diff --git a/nativescript-angular/dom-adapter.ts b/nativescript-angular/dom-adapter.ts index cdca10553..3964978b3 100644 --- a/nativescript-angular/dom-adapter.ts +++ b/nativescript-angular/dom-adapter.ts @@ -49,12 +49,6 @@ export class NativeScriptElementSchemaRegistry extends ElementSchemaRegistry { } } -export class NativeScriptSanitizer extends Sanitizer { - sanitize(context: SecurityContext, value: string): string { - return value; - } -} - export class NativeScriptDomAdapter extends Parse5DomAdapter { static makeCurrent() { rendererLog("Setting DOM"); diff --git a/nativescript-angular/index.ts b/nativescript-angular/index.ts index 751a09787..ef3f466dc 100644 --- a/nativescript-angular/index.ts +++ b/nativescript-angular/index.ts @@ -1,4 +1,5 @@ export * from "./platform"; +export * from "./platform-static"; export * from "./router"; export * from "./forms"; export * from "./http"; diff --git a/nativescript-angular/nativescript.module.ts b/nativescript-angular/nativescript.module.ts index 4278571d4..4f5e7a93a 100644 --- a/nativescript-angular/nativescript.module.ts +++ b/nativescript-angular/nativescript.module.ts @@ -14,13 +14,11 @@ import { ErrorHandler, Renderer, RootRenderer, - Sanitizer, - NgModule, NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA + NgModule, NO_ERRORS_SCHEMA, } from '@angular/core'; import { defaultPageProvider, defaultFrameProvider, defaultDeviceProvider } from "./platform-providers"; -import { NativeScriptSanitizer } from './dom-adapter'; import { NS_DIRECTIVES } from './directives'; import * as nativescriptIntl from "nativescript-intl"; @@ -45,7 +43,6 @@ export function errorHandlerFactory() { { provide: RootRenderer, useClass: NativeScriptRootRenderer }, NativeScriptRenderer, { provide: Renderer, useClass: NativeScriptRenderer }, - { provide: Sanitizer, useClass: NativeScriptSanitizer }, ModalDialogService ], entryComponents: [ diff --git a/nativescript-angular/platform-common.ts b/nativescript-angular/platform-common.ts new file mode 100644 index 000000000..5b01c149e --- /dev/null +++ b/nativescript-angular/platform-common.ts @@ -0,0 +1,194 @@ +// Initial imports and polyfills +import 'globals'; +import './zone.js/dist/zone-nativescript'; +import 'reflect-metadata'; +import './polyfills/array'; +import './polyfills/console'; + +import { + Type, + Injector, + CompilerOptions, + PlatformRef, + NgModuleFactory, + NgModuleRef, + EventEmitter, + Provider, + Sanitizer, + OpaqueToken, +} from '@angular/core'; + +import { rendererLog, rendererError } from "./trace"; +import { PAGE_FACTORY, PageFactory, defaultPageFactoryProvider } from './platform-providers'; + +import * as application from "application"; +import { topmost, NavigationEntry } from "ui/frame"; +import { Page } from 'ui/page'; +import { TextView } from 'ui/text-view'; + +import * as nativescriptIntl from "nativescript-intl"; +global.Intl = nativescriptIntl; + +export const onBeforeLivesync = new EventEmitter>(); +export const onAfterLivesync = new EventEmitter>(); +let lastBootstrappedModule: WeakRef>; +type BootstrapperAction = () => Promise>; + +interface BootstrapParams { + appModuleType: Type; + appOptions?: AppOptions; +} + +export interface AppOptions { + bootInExistingPage: boolean; + cssFile?: string; + startPageActionBarHidden?: boolean; +} + +export type PlatformFactory = (extraProviders?: Provider[]) => PlatformRef; + +export class NativeScriptSanitizer extends Sanitizer { + sanitize(context: any, value: string): string { + return value; + } +} + +export const COMMON_PROVIDERS = [ + defaultPageFactoryProvider, + { provide: Sanitizer, useClass: NativeScriptSanitizer }, +]; + +export class NativeScriptPlatformRef extends PlatformRef { + private _bootstrapper: BootstrapperAction; + + constructor(private platform: PlatformRef, private appOptions?: AppOptions) { + super(); + } + + bootstrapModuleFactory(moduleFactory: NgModuleFactory): Promise> { + this._bootstrapper = () => this.platform.bootstrapModuleFactory(moduleFactory); + + this.bootstrapApp(); + + return null; //Make the compiler happy + } + + bootstrapModule(moduleType: Type, compilerOptions: CompilerOptions | CompilerOptions[] = []): Promise> { + this._bootstrapper = () => this.platform.bootstrapModule(moduleType, compilerOptions); + + this.bootstrapApp(); + + return null; //Make the compiler happy + } + + private bootstrapApp() { + global.__onLiveSyncCore = () => this.livesyncModule(); + + const mainPageEntry = this.createNavigationEntry(this._bootstrapper); + + application.start(mainPageEntry); + } + + livesyncModule(): void { + rendererLog("ANGULAR LiveSync Started"); + + onBeforeLivesync.next(lastBootstrappedModule ? lastBootstrappedModule.get() : null); + + const mainPageEntry = this.createNavigationEntry( + this._bootstrapper, + compRef => onAfterLivesync.next(compRef), + error => onAfterLivesync.error(error), + true + ); + mainPageEntry.animated = false; + mainPageEntry.clearHistory = true; + + const frame = topmost(); + if (frame) { + if (frame.currentPage && frame.currentPage.modal) { + frame.currentPage.modal.closeModal(); + } + frame.navigate(mainPageEntry); + } + } + + onDestroy(callback: () => void): void { + this.platform.onDestroy(callback); + } + + get injector(): Injector { + return this.platform.injector; + }; + + destroy(): void { + this.platform.destroy(); + } + + get destroyed(): boolean { + return this.platform.destroyed; + } + + private createNavigationEntry( + bootstrapAction: BootstrapperAction, + resolve?: (comp: NgModuleRef) => void, + reject?: (e: Error) => void, + isLivesync: boolean = false, + isReboot: boolean = false): NavigationEntry { + + const pageFactory: PageFactory = this.platform.injector.get(PAGE_FACTORY); + + const navEntry: NavigationEntry = { + create: (): Page => { + let page = pageFactory({ isBootstrap: true, isLivesync }); + if (this.appOptions) { + page.actionBarHidden = this.appOptions.startPageActionBarHidden; + } + + let onLoadedHandler = function (args) { + page.off('loaded', onLoadedHandler); + //profiling.stop('application-start'); + rendererLog('Page loaded'); + + //profiling.start('ng-bootstrap'); + rendererLog('BOOTSTRAPPING...'); + bootstrapAction().then((moduleRef) => { + //profiling.stop('ng-bootstrap'); + rendererLog('ANGULAR BOOTSTRAP DONE.'); + lastBootstrappedModule = new WeakRef(moduleRef); + + if (resolve) { + resolve(moduleRef); + } + return moduleRef; + }, (err) => { + rendererError('ERROR BOOTSTRAPPING ANGULAR'); + let errorMessage = err.message + "\n\n" + err.stack; + rendererError(errorMessage); + + let view = new TextView(); + view.text = errorMessage; + page.content = view; + + if (reject) { + reject(err); + } + }); + }; + + page.on('loaded', onLoadedHandler); + + return page; + } + }; + + if (isReboot) { + navEntry.animated = false; + navEntry.clearHistory = true; + } + + return navEntry; + } + + liveSyncApp() { + } +} diff --git a/nativescript-angular/platform-static.ts b/nativescript-angular/platform-static.ts new file mode 100644 index 000000000..9c9d7c32e --- /dev/null +++ b/nativescript-angular/platform-static.ts @@ -0,0 +1,17 @@ +// Always import platform-common first - because polyfills +import { NativeScriptPlatformRef, AppOptions, COMMON_PROVIDERS, PlatformFactory } from "./platform-common"; + +import { platformCore, PlatformRef, createPlatformFactory } from '@angular/core'; + +// "Static" platform +const _platformNativeScript: PlatformFactory = createPlatformFactory( + platformCore, 'nativeScript', [...COMMON_PROVIDERS]); + +export function platformNativeScript(options?: AppOptions, extraProviders?: any[]): PlatformRef { + //Return raw platform to advanced users only if explicitly requested + if (options && options.bootInExistingPage === true) { + return _platformNativeScript(extraProviders); + } else { + return new NativeScriptPlatformRef(_platformNativeScript(extraProviders), options); + } +} diff --git a/nativescript-angular/platform.ts b/nativescript-angular/platform.ts index e3a889070..25fb55b5c 100644 --- a/nativescript-angular/platform.ts +++ b/nativescript-angular/platform.ts @@ -1,9 +1,5 @@ -import 'globals'; -import './zone.js/dist/zone-nativescript'; - -import 'reflect-metadata'; -import './polyfills/array'; -import './polyfills/console'; +// Always import platform-common first - because polyfills +import { NativeScriptPlatformRef, AppOptions, PlatformFactory, COMMON_PROVIDERS } from "./platform-common"; import { ElementSchemaRegistry, @@ -11,43 +7,20 @@ import { COMPILER_PROVIDERS, platformCoreDynamic } from '@angular/compiler'; -import { Provider, platformCore } from '@angular/core'; + import { - Type, - Injector, - CompilerOptions, COMPILER_OPTIONS, + Sanitizer, PlatformRef, - NgModuleFactory, - NgModuleRef, - EventEmitter, OpaqueToken, createPlatformFactory } from '@angular/core'; -import * as application from "application"; -import { topmost, NavigationEntry } from "ui/frame"; -import { Page } from 'ui/page'; -import { rendererLog, rendererError } from "./trace"; -import { TextView } from 'ui/text-view'; import { NativeScriptElementSchemaRegistry } from './dom-adapter'; import { FileSystemResourceLoader } from './resource-loader'; -import { PAGE_FACTORY, PageFactory, defaultPageFactoryProvider } from './platform-providers'; - -import * as nativescriptIntl from "nativescript-intl"; -global.Intl = nativescriptIntl; - -type PlatformFactory = (extraProviders?: Provider[]) => PlatformRef; - export { NativeScriptModule } from "./nativescript.module"; -export interface AppOptions { - bootInExistingPage: boolean, - cssFile?: string; - startPageActionBarHidden?: boolean; -} - export const NS_COMPILER_PROVIDERS = [ COMPILER_PROVIDERS, { @@ -59,154 +32,10 @@ export const NS_COMPILER_PROVIDERS = [ ] }, multi: true - } + }, ]; -const COMMON_PROVIDERS = [ - defaultPageFactoryProvider, -]; - -export const onBeforeLivesync = new EventEmitter>(); -export const onAfterLivesync = new EventEmitter>(); - -type BootstrapperAction = () => Promise>; - -let lastBootstrappedModule: WeakRef>; -interface BootstrapParams { - appModuleType: Type, - appOptions?: AppOptions -} - -class NativeScriptPlatformRef extends PlatformRef { - private _bootstrapper: BootstrapperAction; - - constructor(private platform: PlatformRef, private appOptions?: AppOptions) { - super(); - } - - bootstrapModuleFactory(moduleFactory: NgModuleFactory): Promise> { - this._bootstrapper = () => this.platform.bootstrapModuleFactory(moduleFactory); - - this.bootstrapApp(); - - return null; //Make the compiler happy - } - - bootstrapModule(moduleType: Type, compilerOptions: CompilerOptions | CompilerOptions[] = []): Promise> { - this._bootstrapper = () => this.platform.bootstrapModule(moduleType, compilerOptions); - - this.bootstrapApp(); - - return null; //Make the compiler happy - } - - private bootstrapApp() { - global.__onLiveSyncCore = () => this.livesyncModule(); - - const mainPageEntry = this.createNavigationEntry(this._bootstrapper); - - application.start(mainPageEntry); - } - - livesyncModule(): void { - rendererLog("ANGULAR LiveSync Started"); - - onBeforeLivesync.next(lastBootstrappedModule ? lastBootstrappedModule.get() : null); - - const mainPageEntry = this.createNavigationEntry( - this._bootstrapper, - compRef => onAfterLivesync.next(compRef), - error => onAfterLivesync.error(error), - true - ); - mainPageEntry.animated = false; - mainPageEntry.clearHistory = true; - - const frame = topmost(); - if (frame) { - if (frame.currentPage && frame.currentPage.modal) { - frame.currentPage.modal.closeModal(); - } - frame.navigate(mainPageEntry); - } - } - - onDestroy(callback: () => void): void { - this.platform.onDestroy(callback); - } - - get injector(): Injector { - return this.platform.injector; - }; - - destroy(): void { - this.platform.destroy(); - } - - get destroyed(): boolean { - return this.platform.destroyed; - } - - private createNavigationEntry(bootstrapAction: BootstrapperAction, resolve?: (comp: NgModuleRef) => void, reject?: (e: Error) => void, isLivesync: boolean = false, isReboot: boolean = false): NavigationEntry { - const pageFactory: PageFactory = this.platform.injector.get(PAGE_FACTORY); - - const navEntry: NavigationEntry = { - create: (): Page => { - let page = pageFactory({ isBootstrap: true, isLivesync }); - if (this.appOptions) { - page.actionBarHidden = this.appOptions.startPageActionBarHidden; - } - - let onLoadedHandler = function (args) { - page.off('loaded', onLoadedHandler); - //profiling.stop('application-start'); - rendererLog('Page loaded'); - - //profiling.start('ng-bootstrap'); - rendererLog('BOOTSTRAPPING...'); - bootstrapAction().then((moduleRef) => { - //profiling.stop('ng-bootstrap'); - rendererLog('ANGULAR BOOTSTRAP DONE.'); - lastBootstrappedModule = new WeakRef(moduleRef); - - if (resolve) { - resolve(moduleRef); - } - return moduleRef; - }, (err) => { - rendererError('ERROR BOOTSTRAPPING ANGULAR'); - let errorMessage = err.message + "\n\n" + err.stack; - rendererError(errorMessage); - - let view = new TextView(); - view.text = errorMessage; - page.content = view; - - if (reject) { - reject(err); - } - }); - }; - - page.on('loaded', onLoadedHandler); - - return page; - } - }; - - if (isReboot) { - navEntry.animated = false; - navEntry.clearHistory = true; - } - - return navEntry; - } - - liveSyncApp() { - } -} - -// Dynamic platfrom +// Dynamic platform const _platformNativeScriptDynamic: PlatformFactory = createPlatformFactory( platformCoreDynamic, 'nativeScriptDynamic', [...COMMON_PROVIDERS, ...NS_COMPILER_PROVIDERS]); @@ -217,17 +46,4 @@ export function platformNativeScriptDynamic(options?: AppOptions, extraProviders } else { return new NativeScriptPlatformRef(_platformNativeScriptDynamic(extraProviders), options); } -} - -// "Static" platform -const _platformNativeScript: PlatformFactory = createPlatformFactory( - platformCore, 'nativeScript', [...COMMON_PROVIDERS]); - -export function platformNativeScript(options?: AppOptions, extraProviders?: any[]): PlatformRef { - //Return raw platform to advanced users only if explicitly requested - if (options && options.bootInExistingPage === true) { - return _platformNativeScript(extraProviders); - } else { - return new NativeScriptPlatformRef(_platformNativeScript(extraProviders), options); - } -} +} \ No newline at end of file diff --git a/nativescript-angular/router/ns-router-link-active.ts b/nativescript-angular/router/ns-router-link-active.ts index 679007535..94af17ce5 100644 --- a/nativescript-angular/router/ns-router-link-active.ts +++ b/nativescript-angular/router/ns-router-link-active.ts @@ -56,7 +56,7 @@ export class NSRouterLinkActive implements OnChanges, OnDestroy, AfterContentIni private classes: string[] = []; private subscription: Subscription; - @Input() private nsRouterLinkActiveOptions: { exact: boolean } = { exact: false }; + @Input() nsRouterLinkActiveOptions: { exact: boolean } = { exact: false }; constructor(private router: Router, private element: ElementRef, private renderer: Renderer) { this.subscription = router.events.subscribe(s => { diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 0d8852b2c..baa9261ff 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -13,7 +13,7 @@ import { DetachedLoader } from "../common/detached-loader"; import { ViewUtil } from "../view-util"; import { Frame } from "ui/frame"; import { Page, NavigatedData } from "ui/page"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject } from "rxjs/BehaviorSubject"; interface CacheItem { componentRef: ComponentRef; From a40a71c9374d9e79be292819f2bb9b11010bb655 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 11 Nov 2016 17:06:55 +0200 Subject: [PATCH 2/5] fix(page-router-outlet): make page-router-outlet lazy-loadable --- .../router/page-router-outlet.ts | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 0d8852b2c..aadc89e93 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -152,15 +152,16 @@ export class PageRouterOutlet { if (this.locationStrategy._isPageNavigatingBack()) { this.activateOnGoBack(activatedRoute, providers, outletMap); } else { - this.activateOnGoForward(activatedRoute, providers, outletMap); + this.activateOnGoForward(activatedRoute, providers, outletMap, loadedResolver); } } private activateOnGoForward( activatedRoute: ActivatedRoute, providers: ResolvedReflectiveProvider[], - outletMap: RouterOutletMap): void { - const factory = this.getComponentFactory(activatedRoute); + outletMap: RouterOutletMap, + loadedResolver: ComponentFactoryResolver): void { + const factory = this.getComponentFactory(activatedRoute, loadedResolver); const pageRoute = new PageRoute(activatedRoute); providers = [...providers, ...ReflectiveInjector.resolve([{ provide: PageRoute, useValue: pageRoute }])]; @@ -241,31 +242,19 @@ export class PageRouterOutlet { } // NOTE: Using private APIs - potential break point! - private getComponentFactory(activatedRoute: any): ComponentFactory { + private getComponentFactory(activatedRoute: any, loadedResolver: ComponentFactoryResolver): ComponentFactory { const snapshot = activatedRoute._futureSnapshot; const component = snapshot._routeConfig.component; let factory: ComponentFactory; - try { - factory = typeof component === 'string' ? - snapshot._resolvedComponentFactory : - this.componentFactoryResolver.resolveComponentFactory(component); - } catch (e) { - if (!(e.constructor.name === "NoComponentFactoryError")) { - throw e; - } - // TODO: vsavkin uncomment this once ComponentResolver is deprecated - // const componentName = component ? component.name : null; - // console.warn( - // `'${componentName}' not found in precompile array. To ensure all components referred - // to by the RouterConfig are compiled, you must add '${componentName}' to the - // 'precompile' array of your application component. This will be required in a future - // release of the router.`); - - factory = snapshot._resolvedComponentFactory; + + if (loadedResolver) { + factory = loadedResolver.resolveComponentFactory(component); + } else { + factory = this.componentFactoryResolver.resolveComponentFactory(component); } + return factory; } - } function log(msg: string) { From c1816ce3a010935cd5b3074edd6944ee92238576 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 15 Nov 2016 14:14:12 +0200 Subject: [PATCH 3/5] tests(lazy-loading): add e2e test for lazy-loaded module --- tests/app/lazy-load-main.ts | 17 ++++++++ tests/app/lazy-loaded.module.ts | 20 ++++++++++ tests/app/main.ts | 34 +++++++++++++++- tests/e2e-tests/lazy-load-routing.js | 59 ++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/app/lazy-load-main.ts create mode 100644 tests/app/lazy-loaded.module.ts create mode 100644 tests/e2e-tests/lazy-load-routing.js diff --git a/tests/app/lazy-load-main.ts b/tests/app/lazy-load-main.ts new file mode 100644 index 000000000..f7ac1fb02 --- /dev/null +++ b/tests/app/lazy-load-main.ts @@ -0,0 +1,17 @@ +import { Component } from "@angular/core"; +import { FirstComponent } from "./first.component"; + +@Component({ + template: ` + + + ` +}) +export class LazyLoadMain { +} + +export const routes = [ + { path: "", redirectTo: "first/lazy-load", pathMatch: "full" }, + { path: "first/:id", component: FirstComponent }, + { path: "second", loadChildren: () => require("./lazy-loaded.module")["SecondModule"] } +]; diff --git a/tests/app/lazy-loaded.module.ts b/tests/app/lazy-loaded.module.ts new file mode 100644 index 000000000..85caabed9 --- /dev/null +++ b/tests/app/lazy-loaded.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { NativeScriptModule } from "nativescript-angular"; +import { NativeScriptRouterModule } from "nativescript-angular/router"; + +import { SecondComponent } from './second.component'; + +const routes = [ + { path: ":id", component: SecondComponent }, +]; + +@NgModule({ + declarations: [SecondComponent], + imports: [ + NativeScriptModule, + NativeScriptRouterModule, + NativeScriptRouterModule.forChild(routes) + ] +}) +export class SecondModule { } + diff --git a/tests/app/main.ts b/tests/app/main.ts index 643ca4f93..64d4b97d9 100644 --- a/tests/app/main.ts +++ b/tests/app/main.ts @@ -16,6 +16,7 @@ import "ui/frame"; import {HOOKS_LOG} from "./base.component"; import {MultiPageMain, routes as multiPageRoutes} from "./multi-page-main.component"; import {SinglePageMain, routes as singlePageRoutes} from "./single-page-main.component"; +import {LazyLoadMain, routes as lazyLoadRoutes} from "./lazy-load-main"; import {FirstComponent} from "./first.component"; import {SecondComponent} from "./second.component"; import { OpaqueToken, NgModule } from "@angular/core"; @@ -42,6 +43,8 @@ const singlePageHooksLog = new BehaviorSubject([]); const singlePageHooksLogProvider = {provide: HOOKS_LOG, useValue: singlePageHooksLog}; const multiPageHooksLog = new BehaviorSubject([]); const multiPageHooksLogProvider = {provide: HOOKS_LOG, useValue: multiPageHooksLog}; +const lazyLoadHooksLog = new BehaviorSubject([]); +const lazyLoadHooksLogProvider = {provide: HOOKS_LOG, useValue: lazyLoadHooksLog}; @NgModule({ bootstrap: [ @@ -107,6 +110,34 @@ class SinglePageModule {} }) class MultiPageModule {} +@NgModule({ + bootstrap: [ + LazyLoadMain + ], + declarations: [ + LazyLoadMain, + FirstComponent, + ], + entryComponents: [ + LazyLoadMain, + ], + imports: [ + NativeScriptModule, + NativeScriptFormsModule, + NativeScriptRouterModule, + NativeScriptRouterModule.forRoot(lazyLoadRoutes), + ], + exports: [ + NativeScriptModule, + NativeScriptFormsModule, + NativeScriptRouterModule, + ], + providers: [ + rootViewProvider, + lazyLoadHooksLogProvider, + ] +}) +class LazyLoadModule {} application.start({ create: (): Page => { @@ -120,9 +151,10 @@ application.start({ //profiling.start('ng-bootstrap'); console.log('BOOTSTRAPPING TEST APPS...'); - + platform.bootstrapModule(SinglePageModule); platform.bootstrapModule(MultiPageModule); + platform.bootstrapModule(LazyLoadModule); } page.on('loaded', onLoadedHandler); diff --git a/tests/e2e-tests/lazy-load-routing.js b/tests/e2e-tests/lazy-load-routing.js new file mode 100644 index 000000000..b40341a83 --- /dev/null +++ b/tests/e2e-tests/lazy-load-routing.js @@ -0,0 +1,59 @@ +"use strict"; +var nsAppium = require("nativescript-dev-appium"); + +describe("lazy load routing", function () { + this.timeout(360000); + var driver; + + before(function () { + var caps = { + browserName: '', + 'appium-version': '1.6', + platformName: 'Android', + platformVersion: '4.4.4', + deviceName: 'Android Emulator', + app: undefined // will be set later + }; + driver = nsAppium.createDriver(caps); + }); + + after(function () { + return driver + .quit() + .finally(function () { + console.log("Driver quit successfully"); + }); + }); + + it("loads default path", function () { + return driver + .waitForElementByAccessibilityId("first-lazy-load", 300000) + .elementByAccessibilityId("first-lazy-load") + .should.eventually.exist + .text().should.eventually.equal("First: lazy-load") + }); + + it("navigates and returns", function () { + var expectedHookLog = [ + "first.init", // <-- load + "second.init", // <-- forward (first component is not destroyed) + "second.destroy"] // <-- back (first component is reused) + .join(","); + return driver + .waitForElementByAccessibilityId("first-navigate-lazy-load", 300000) + .elementByAccessibilityId("first-navigate-lazy-load") + .should.eventually.exist + .tap() + .elementByAccessibilityId("second-lazy-load") + .should.eventually.exist + .text().should.eventually.equal("Second: lazy-load") + .elementByAccessibilityId("second-navigate-back-lazy-load") + .should.eventually.exist + .tap() + .elementByAccessibilityId("first-lazy-load") + .should.eventually.exist + .text().should.eventually.equal("First: lazy-load") + .elementByAccessibilityId("hooks-log-lazy-load") + .text().should.eventually.equal(expectedHookLog) + }); +}); From e9fc5d1034929a83d9b94ddb1045295faffad77c Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Fri, 11 Nov 2016 17:06:55 +0200 Subject: [PATCH 4/5] fix(page-router-outlet): make page-router-outlet lazy-loadable --- .../router/page-router-outlet.ts | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index baa9261ff..3585e613b 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -152,15 +152,16 @@ export class PageRouterOutlet { if (this.locationStrategy._isPageNavigatingBack()) { this.activateOnGoBack(activatedRoute, providers, outletMap); } else { - this.activateOnGoForward(activatedRoute, providers, outletMap); + this.activateOnGoForward(activatedRoute, providers, outletMap, loadedResolver); } } private activateOnGoForward( activatedRoute: ActivatedRoute, providers: ResolvedReflectiveProvider[], - outletMap: RouterOutletMap): void { - const factory = this.getComponentFactory(activatedRoute); + outletMap: RouterOutletMap, + loadedResolver: ComponentFactoryResolver): void { + const factory = this.getComponentFactory(activatedRoute, loadedResolver); const pageRoute = new PageRoute(activatedRoute); providers = [...providers, ...ReflectiveInjector.resolve([{ provide: PageRoute, useValue: pageRoute }])]; @@ -241,31 +242,19 @@ export class PageRouterOutlet { } // NOTE: Using private APIs - potential break point! - private getComponentFactory(activatedRoute: any): ComponentFactory { + private getComponentFactory(activatedRoute: any, loadedResolver: ComponentFactoryResolver): ComponentFactory { const snapshot = activatedRoute._futureSnapshot; const component = snapshot._routeConfig.component; let factory: ComponentFactory; - try { - factory = typeof component === 'string' ? - snapshot._resolvedComponentFactory : - this.componentFactoryResolver.resolveComponentFactory(component); - } catch (e) { - if (!(e.constructor.name === "NoComponentFactoryError")) { - throw e; - } - // TODO: vsavkin uncomment this once ComponentResolver is deprecated - // const componentName = component ? component.name : null; - // console.warn( - // `'${componentName}' not found in precompile array. To ensure all components referred - // to by the RouterConfig are compiled, you must add '${componentName}' to the - // 'precompile' array of your application component. This will be required in a future - // release of the router.`); - - factory = snapshot._resolvedComponentFactory; + + if (loadedResolver) { + factory = loadedResolver.resolveComponentFactory(component); + } else { + factory = this.componentFactoryResolver.resolveComponentFactory(component); } + return factory; } - } function log(msg: string) { From 801fce1b4523224619fc430a55a3301435d9a820 Mon Sep 17 00:00:00 2001 From: sis0k0 Date: Tue, 15 Nov 2016 14:14:12 +0200 Subject: [PATCH 5/5] tests(lazy-loading): add e2e test for lazy-loaded module --- tests/app/lazy-load-main.ts | 17 ++++++++ tests/app/lazy-loaded.module.ts | 20 ++++++++++ tests/app/main.ts | 34 +++++++++++++++- tests/e2e-tests/lazy-load-routing.js | 59 ++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/app/lazy-load-main.ts create mode 100644 tests/app/lazy-loaded.module.ts create mode 100644 tests/e2e-tests/lazy-load-routing.js diff --git a/tests/app/lazy-load-main.ts b/tests/app/lazy-load-main.ts new file mode 100644 index 000000000..f7ac1fb02 --- /dev/null +++ b/tests/app/lazy-load-main.ts @@ -0,0 +1,17 @@ +import { Component } from "@angular/core"; +import { FirstComponent } from "./first.component"; + +@Component({ + template: ` + + + ` +}) +export class LazyLoadMain { +} + +export const routes = [ + { path: "", redirectTo: "first/lazy-load", pathMatch: "full" }, + { path: "first/:id", component: FirstComponent }, + { path: "second", loadChildren: () => require("./lazy-loaded.module")["SecondModule"] } +]; diff --git a/tests/app/lazy-loaded.module.ts b/tests/app/lazy-loaded.module.ts new file mode 100644 index 000000000..85caabed9 --- /dev/null +++ b/tests/app/lazy-loaded.module.ts @@ -0,0 +1,20 @@ +import { NgModule } from '@angular/core'; +import { NativeScriptModule } from "nativescript-angular"; +import { NativeScriptRouterModule } from "nativescript-angular/router"; + +import { SecondComponent } from './second.component'; + +const routes = [ + { path: ":id", component: SecondComponent }, +]; + +@NgModule({ + declarations: [SecondComponent], + imports: [ + NativeScriptModule, + NativeScriptRouterModule, + NativeScriptRouterModule.forChild(routes) + ] +}) +export class SecondModule { } + diff --git a/tests/app/main.ts b/tests/app/main.ts index 643ca4f93..64d4b97d9 100644 --- a/tests/app/main.ts +++ b/tests/app/main.ts @@ -16,6 +16,7 @@ import "ui/frame"; import {HOOKS_LOG} from "./base.component"; import {MultiPageMain, routes as multiPageRoutes} from "./multi-page-main.component"; import {SinglePageMain, routes as singlePageRoutes} from "./single-page-main.component"; +import {LazyLoadMain, routes as lazyLoadRoutes} from "./lazy-load-main"; import {FirstComponent} from "./first.component"; import {SecondComponent} from "./second.component"; import { OpaqueToken, NgModule } from "@angular/core"; @@ -42,6 +43,8 @@ const singlePageHooksLog = new BehaviorSubject([]); const singlePageHooksLogProvider = {provide: HOOKS_LOG, useValue: singlePageHooksLog}; const multiPageHooksLog = new BehaviorSubject([]); const multiPageHooksLogProvider = {provide: HOOKS_LOG, useValue: multiPageHooksLog}; +const lazyLoadHooksLog = new BehaviorSubject([]); +const lazyLoadHooksLogProvider = {provide: HOOKS_LOG, useValue: lazyLoadHooksLog}; @NgModule({ bootstrap: [ @@ -107,6 +110,34 @@ class SinglePageModule {} }) class MultiPageModule {} +@NgModule({ + bootstrap: [ + LazyLoadMain + ], + declarations: [ + LazyLoadMain, + FirstComponent, + ], + entryComponents: [ + LazyLoadMain, + ], + imports: [ + NativeScriptModule, + NativeScriptFormsModule, + NativeScriptRouterModule, + NativeScriptRouterModule.forRoot(lazyLoadRoutes), + ], + exports: [ + NativeScriptModule, + NativeScriptFormsModule, + NativeScriptRouterModule, + ], + providers: [ + rootViewProvider, + lazyLoadHooksLogProvider, + ] +}) +class LazyLoadModule {} application.start({ create: (): Page => { @@ -120,9 +151,10 @@ application.start({ //profiling.start('ng-bootstrap'); console.log('BOOTSTRAPPING TEST APPS...'); - + platform.bootstrapModule(SinglePageModule); platform.bootstrapModule(MultiPageModule); + platform.bootstrapModule(LazyLoadModule); } page.on('loaded', onLoadedHandler); diff --git a/tests/e2e-tests/lazy-load-routing.js b/tests/e2e-tests/lazy-load-routing.js new file mode 100644 index 000000000..b40341a83 --- /dev/null +++ b/tests/e2e-tests/lazy-load-routing.js @@ -0,0 +1,59 @@ +"use strict"; +var nsAppium = require("nativescript-dev-appium"); + +describe("lazy load routing", function () { + this.timeout(360000); + var driver; + + before(function () { + var caps = { + browserName: '', + 'appium-version': '1.6', + platformName: 'Android', + platformVersion: '4.4.4', + deviceName: 'Android Emulator', + app: undefined // will be set later + }; + driver = nsAppium.createDriver(caps); + }); + + after(function () { + return driver + .quit() + .finally(function () { + console.log("Driver quit successfully"); + }); + }); + + it("loads default path", function () { + return driver + .waitForElementByAccessibilityId("first-lazy-load", 300000) + .elementByAccessibilityId("first-lazy-load") + .should.eventually.exist + .text().should.eventually.equal("First: lazy-load") + }); + + it("navigates and returns", function () { + var expectedHookLog = [ + "first.init", // <-- load + "second.init", // <-- forward (first component is not destroyed) + "second.destroy"] // <-- back (first component is reused) + .join(","); + return driver + .waitForElementByAccessibilityId("first-navigate-lazy-load", 300000) + .elementByAccessibilityId("first-navigate-lazy-load") + .should.eventually.exist + .tap() + .elementByAccessibilityId("second-lazy-load") + .should.eventually.exist + .text().should.eventually.equal("Second: lazy-load") + .elementByAccessibilityId("second-navigate-back-lazy-load") + .should.eventually.exist + .tap() + .elementByAccessibilityId("first-lazy-load") + .should.eventually.exist + .text().should.eventually.equal("First: lazy-load") + .elementByAccessibilityId("hooks-log-lazy-load") + .text().should.eventually.equal(expectedHookLog) + }); +});