diff --git a/modules/@angular/router/index.ts b/modules/@angular/router/index.ts index e8990c317a..74c9d8e6da 100644 --- a/modules/@angular/router/index.ts +++ b/modules/@angular/router/index.ts @@ -8,7 +8,7 @@ export {ExtraOptions, provideRouterConfig, provideRoutes} from './src/common_router_providers'; -export {Data, ResolveData, Route, RouterConfig, Routes} from './src/config'; +export {Data, LoadChildren, LoadChildrenCallback, ResolveData, Route, RouterConfig, Routes} from './src/config'; export {RouterLink, RouterLinkWithHref} from './src/directives/router_link'; export {RouterLinkActive} from './src/directives/router_link_active'; export {RouterOutlet} from './src/directives/router_outlet'; diff --git a/modules/@angular/router/src/common_router_providers.ts b/modules/@angular/router/src/common_router_providers.ts index 38a1dbd8ba..acc19038c1 100644 --- a/modules/@angular/router/src/common_router_providers.ts +++ b/modules/@angular/router/src/common_router_providers.ts @@ -7,7 +7,7 @@ */ import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common'; -import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core'; +import {ANALYZE_FOR_ENTRY_COMPONENTS, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, ApplicationRef, Compiler, ComponentResolver, Injector, NgModuleFactoryLoader, OpaqueToken, SystemJsNgModuleLoader} from '@angular/core'; import {Route, Routes} from './config'; import {Router} from './router'; @@ -30,13 +30,13 @@ export interface ExtraOptions { export function setupRouter( ref: ApplicationRef, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, - loader: NgModuleFactoryLoader, config: Route[][], opts: ExtraOptions = {}) { + loader: NgModuleFactoryLoader, compiler: Compiler, config: Route[][], opts: ExtraOptions = {}) { if (ref.componentTypes.length == 0) { throw new Error('Bootstrap at least one component before injecting Router.'); } const componentType = ref.componentTypes[0]; const r = new Router( - componentType, resolver, urlSerializer, outletMap, location, injector, loader, + componentType, resolver, urlSerializer, outletMap, location, injector, loader, compiler, flatten(config)); if (opts.enableTracing) { @@ -92,7 +92,7 @@ export function provideRouter(routes: Routes, config: ExtraOptions = {}): any[] useFactory: setupRouter, deps: [ ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector, - NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION + NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION ] }, diff --git a/modules/@angular/router/src/config.ts b/modules/@angular/router/src/config.ts index 61a6310427..210938c5aa 100644 --- a/modules/@angular/router/src/config.ts +++ b/modules/@angular/router/src/config.ts @@ -7,6 +7,7 @@ */ import {Type} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; /** @@ -474,6 +475,16 @@ export type ResolveData = { [name: string]: any }; +/** + * @experimental + */ +export type LoadChildrenCallback = () => Type| Promise>| Observable>; + +/** + * @experimental + */ +export type LoadChildren = string | LoadChildrenCallback; + /** * See {@link Routes} for more details. * @stable @@ -496,7 +507,7 @@ export interface Route { data?: Data; resolve?: ResolveData; children?: Route[]; - loadChildren?: string; + loadChildren?: LoadChildren; } export function validateConfig(config: Routes): void { diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 219ba22262..f9030549fc 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -13,7 +13,7 @@ import 'rxjs/add/operator/reduce'; import 'rxjs/add/operator/every'; import {Location} from '@angular/common'; -import {ComponentFactoryResolver, ComponentResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core'; +import {Compiler, ComponentFactoryResolver, ComponentResolver, Injector, NgModuleFactoryLoader, ReflectiveInjector, Type} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Subject} from 'rxjs/Subject'; import {Subscription} from 'rxjs/Subscription'; @@ -148,11 +148,11 @@ export class Router { private rootComponentType: Type, private resolver: ComponentResolver, private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap, private location: Location, private injector: Injector, loader: NgModuleFactoryLoader, - public config: Routes) { + compiler: Compiler, public config: Routes) { this.resetConfig(config); this.routerEvents = new Subject(); this.currentUrlTree = createEmptyUrlTree(); - this.configLoader = new RouterConfigLoader(loader); + this.configLoader = new RouterConfigLoader(loader, compiler); this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType); } diff --git a/modules/@angular/router/src/router_config_loader.ts b/modules/@angular/router/src/router_config_loader.ts index a3ef2f475a..f0e01fe9c0 100644 --- a/modules/@angular/router/src/router_config_loader.ts +++ b/modules/@angular/router/src/router_config_loader.ts @@ -6,12 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {ComponentFactoryResolver, Injector, NgModuleFactoryLoader, OpaqueToken} from '@angular/core'; +import {Compiler, ComponentFactoryResolver, Injector, NgModuleFactory, NgModuleFactoryLoader, OpaqueToken} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {fromPromise} from 'rxjs/observable/fromPromise'; +import {of } from 'rxjs/observable/of'; + +import {LoadChildren, Route} from './config'; +import {flatten, wrapIntoObservable} from './utils/collection'; -import {Route} from './config'; -import {flatten} from './utils/collection'; /** @@ -27,13 +29,24 @@ export class LoadedRouterConfig { } export class RouterConfigLoader { - constructor(private loader: NgModuleFactoryLoader) {} + constructor(private loader: NgModuleFactoryLoader, private compiler: Compiler) {} - load(parentInjector: Injector, path: string): Observable { - return fromPromise(this.loader.load(path).then(r => { + load(parentInjector: Injector, loadChildren: LoadChildren): Observable { + return this.loadModuleFactory(loadChildren).map(r => { const ref = r.create(parentInjector); return new LoadedRouterConfig( flatten(ref.injector.get(ROUTES)), ref.injector, ref.componentFactoryResolver); - })); + }); } -} + + private loadModuleFactory(loadChildren: LoadChildren): Observable> { + if (typeof loadChildren === 'string') { + return fromPromise(this.loader.load(loadChildren)); + } else { + const offlineMode = this.compiler instanceof Compiler; + return wrapIntoObservable(loadChildren()) + .mergeMap( + t => offlineMode ? of (t) : fromPromise(this.compiler.compileModuleAsync(t))); + } + } +} \ No newline at end of file diff --git a/modules/@angular/router/src/router_module.ts b/modules/@angular/router/src/router_module.ts index 646d47f545..a667d5f66c 100644 --- a/modules/@angular/router/src/router_module.ts +++ b/modules/@angular/router/src/router_module.ts @@ -7,7 +7,7 @@ */ import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common'; -import {ApplicationRef, ComponentResolver, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core'; +import {ApplicationRef, Compiler, ComponentResolver, Inject, Injector, ModuleWithProviders, NgModule, NgModuleFactoryLoader, OpaqueToken, Optional, SystemJsNgModuleLoader} from '@angular/core'; import {ExtraOptions, ROUTER_CONFIGURATION, provideRouterConfig, provideRouterInitializer, provideRoutes, rootRoute, setupRouter} from './common_router_providers'; import {Routes} from './config'; @@ -42,7 +42,7 @@ export const ROUTER_PROVIDERS: any[] = [ useFactory: setupRouter, deps: [ ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector, - NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION + NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION ] }, RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]}, diff --git a/modules/@angular/router/src/utils/collection.ts b/modules/@angular/router/src/utils/collection.ts index 551d076140..37ae7a5c6a 100644 --- a/modules/@angular/router/src/utils/collection.ts +++ b/modules/@angular/router/src/utils/collection.ts @@ -122,7 +122,7 @@ export function andObservables(observables: Observable>): Observ return observables.mergeAll().every(result => result === true); } -export function wrapIntoObservable(value: T | Observable): Observable { +export function wrapIntoObservable(value: T | Promise| Observable): Observable { if (value instanceof Observable) { return value; } else if (value instanceof Promise) { diff --git a/modules/@angular/router/test/integration.spec.ts b/modules/@angular/router/test/integration.spec.ts index ea24355594..4569f1fc8b 100644 --- a/modules/@angular/router/test/integration.spec.ts +++ b/modules/@angular/router/test/integration.spec.ts @@ -1608,6 +1608,33 @@ describe('Integration', () => { expect(fixture.debugElement.nativeElement).toHaveText('lazy-loaded'); }))); + it('works when given a callback', + fakeAsync(inject( + [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], + (router: Router, tcb: TestComponentBuilder, location: Location) => { + @Component({selector: 'lazy', template: 'lazy-loaded'}) + class LazyLoadedComponent { + } + + @NgModule({ + declarations: [LazyLoadedComponent], + imports: [RouterModule.forChild([{path: 'loaded', component: LazyLoadedComponent}])], + entryComponents: [LazyLoadedComponent] + }) + class LoadedModule { + } + + const fixture = createRoot(tcb, router, RootCmp); + + router.resetConfig([{path: 'lazy', loadChildren: () => LoadedModule}]); + + router.navigateByUrl('/lazy/loaded'); + advance(fixture); + + expect(location.path()).toEqual('/lazy/loaded'); + expect(fixture.debugElement.nativeElement).toHaveText('lazy-loaded'); + }))); + it('error emit an error when cannot load a config', fakeAsync(inject( [Router, TestComponentBuilder, Location, NgModuleFactoryLoader], diff --git a/modules/@angular/router/testing/router_testing_module.ts b/modules/@angular/router/testing/router_testing_module.ts index e3c0346108..72c98ec8d0 100644 --- a/modules/@angular/router/testing/router_testing_module.ts +++ b/modules/@angular/router/testing/router_testing_module.ts @@ -40,9 +40,11 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader { function setupTestingRouter( resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, - location: Location, loader: NgModuleFactoryLoader, injector: Injector, routes: Route[][]) { + location: Location, loader: NgModuleFactoryLoader, compiler: Compiler, injector: Injector, + routes: Route[][]) { return new Router( - null, resolver, urlSerializer, outletMap, location, injector, loader, flatten(routes)); + null, resolver, urlSerializer, outletMap, location, injector, loader, compiler, + flatten(routes)); } /** @@ -75,7 +77,7 @@ function setupTestingRouter( useFactory: setupTestingRouter, deps: [ ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader, - Injector, ROUTES + Compiler, Injector, ROUTES ] }, ] diff --git a/tools/public_api_guard/router/index.d.ts b/tools/public_api_guard/router/index.d.ts index 3d37d00660..4b28c76abf 100644 --- a/tools/public_api_guard/router/index.d.ts +++ b/tools/public_api_guard/router/index.d.ts @@ -75,6 +75,12 @@ export interface ExtraOptions { useHash?: boolean; } +/** @experimental */ +export declare type LoadChildren = string | LoadChildrenCallback; + +/** @experimental */ +export declare type LoadChildrenCallback = () => Type | Promise> | Observable>; + /** @stable */ export declare class NavigationCancel { id: number; @@ -156,7 +162,7 @@ export interface Route { children?: Route[]; component?: Type | string; data?: Data; - loadChildren?: string; + loadChildren?: LoadChildren; outlet?: string; path?: string; pathMatch?: string; @@ -172,7 +178,7 @@ export declare class Router { /** @experimental */ navigated: boolean; routerState: RouterState; url: string; - constructor(rootComponentType: Type, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, config: Routes); + constructor(rootComponentType: Type, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, compiler: Compiler, config: Routes); createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree; dispose(): void; initialNavigation(): void;