feat(router): extend support for lazy loading children (#10705)
This commit is contained in:
parent
bec5c5fdad
commit
6b26102931
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
|
|
||||||
export {ExtraOptions, provideRouterConfig, provideRoutes} from './src/common_router_providers';
|
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 {RouterLink, RouterLinkWithHref} from './src/directives/router_link';
|
||||||
export {RouterLinkActive} from './src/directives/router_link_active';
|
export {RouterLinkActive} from './src/directives/router_link_active';
|
||||||
export {RouterOutlet} from './src/directives/router_outlet';
|
export {RouterOutlet} from './src/directives/router_outlet';
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Location, LocationStrategy, PathLocationStrategy} from '@angular/common';
|
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 {Route, Routes} from './config';
|
||||||
import {Router} from './router';
|
import {Router} from './router';
|
||||||
|
@ -30,13 +30,13 @@ export interface ExtraOptions {
|
||||||
export function setupRouter(
|
export function setupRouter(
|
||||||
ref: ApplicationRef, resolver: ComponentResolver, urlSerializer: UrlSerializer,
|
ref: ApplicationRef, resolver: ComponentResolver, urlSerializer: UrlSerializer,
|
||||||
outletMap: RouterOutletMap, location: Location, injector: Injector,
|
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) {
|
if (ref.componentTypes.length == 0) {
|
||||||
throw new Error('Bootstrap at least one component before injecting Router.');
|
throw new Error('Bootstrap at least one component before injecting Router.');
|
||||||
}
|
}
|
||||||
const componentType = ref.componentTypes[0];
|
const componentType = ref.componentTypes[0];
|
||||||
const r = new Router(
|
const r = new Router(
|
||||||
componentType, resolver, urlSerializer, outletMap, location, injector, loader,
|
componentType, resolver, urlSerializer, outletMap, location, injector, loader, compiler,
|
||||||
flatten(config));
|
flatten(config));
|
||||||
|
|
||||||
if (opts.enableTracing) {
|
if (opts.enableTracing) {
|
||||||
|
@ -92,7 +92,7 @@ export function provideRouter(routes: Routes, config: ExtraOptions = {}): any[]
|
||||||
useFactory: setupRouter,
|
useFactory: setupRouter,
|
||||||
deps: [
|
deps: [
|
||||||
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
|
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
|
||||||
NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
|
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Type} from '@angular/core';
|
import {Type} from '@angular/core';
|
||||||
|
import {Observable} from 'rxjs/Observable';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -474,6 +475,16 @@ export type ResolveData = {
|
||||||
[name: string]: any
|
[name: string]: any
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export type LoadChildrenCallback = () => Type<any>| Promise<Type<any>>| Observable<Type<any>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
export type LoadChildren = string | LoadChildrenCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See {@link Routes} for more details.
|
* See {@link Routes} for more details.
|
||||||
* @stable
|
* @stable
|
||||||
|
@ -496,7 +507,7 @@ export interface Route {
|
||||||
data?: Data;
|
data?: Data;
|
||||||
resolve?: ResolveData;
|
resolve?: ResolveData;
|
||||||
children?: Route[];
|
children?: Route[];
|
||||||
loadChildren?: string;
|
loadChildren?: LoadChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validateConfig(config: Routes): void {
|
export function validateConfig(config: Routes): void {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import 'rxjs/add/operator/reduce';
|
||||||
import 'rxjs/add/operator/every';
|
import 'rxjs/add/operator/every';
|
||||||
|
|
||||||
import {Location} from '@angular/common';
|
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 {Observable} from 'rxjs/Observable';
|
||||||
import {Subject} from 'rxjs/Subject';
|
import {Subject} from 'rxjs/Subject';
|
||||||
import {Subscription} from 'rxjs/Subscription';
|
import {Subscription} from 'rxjs/Subscription';
|
||||||
|
@ -148,11 +148,11 @@ export class Router {
|
||||||
private rootComponentType: Type<any>, private resolver: ComponentResolver,
|
private rootComponentType: Type<any>, private resolver: ComponentResolver,
|
||||||
private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap,
|
private urlSerializer: UrlSerializer, private outletMap: RouterOutletMap,
|
||||||
private location: Location, private injector: Injector, loader: NgModuleFactoryLoader,
|
private location: Location, private injector: Injector, loader: NgModuleFactoryLoader,
|
||||||
public config: Routes) {
|
compiler: Compiler, public config: Routes) {
|
||||||
this.resetConfig(config);
|
this.resetConfig(config);
|
||||||
this.routerEvents = new Subject<Event>();
|
this.routerEvents = new Subject<Event>();
|
||||||
this.currentUrlTree = createEmptyUrlTree();
|
this.currentUrlTree = createEmptyUrlTree();
|
||||||
this.configLoader = new RouterConfigLoader(loader);
|
this.configLoader = new RouterConfigLoader(loader, compiler);
|
||||||
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
|
this.currentRouterState = createEmptyState(this.currentUrlTree, this.rootComponentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,14 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* 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 {Observable} from 'rxjs/Observable';
|
||||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
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 {
|
export class RouterConfigLoader {
|
||||||
constructor(private loader: NgModuleFactoryLoader) {}
|
constructor(private loader: NgModuleFactoryLoader, private compiler: Compiler) {}
|
||||||
|
|
||||||
load(parentInjector: Injector, path: string): Observable<LoadedRouterConfig> {
|
load(parentInjector: Injector, loadChildren: LoadChildren): Observable<LoadedRouterConfig> {
|
||||||
return fromPromise(this.loader.load(path).then(r => {
|
return this.loadModuleFactory(loadChildren).map(r => {
|
||||||
const ref = r.create(parentInjector);
|
const ref = r.create(parentInjector);
|
||||||
return new LoadedRouterConfig(
|
return new LoadedRouterConfig(
|
||||||
flatten(ref.injector.get(ROUTES)), ref.injector, ref.componentFactoryResolver);
|
flatten(ref.injector.get(ROUTES)), ref.injector, ref.componentFactoryResolver);
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private loadModuleFactory(loadChildren: LoadChildren): Observable<NgModuleFactory<any>> {
|
||||||
|
if (typeof loadChildren === 'string') {
|
||||||
|
return fromPromise(this.loader.load(loadChildren));
|
||||||
|
} else {
|
||||||
|
const offlineMode = this.compiler instanceof Compiler;
|
||||||
|
return wrapIntoObservable(loadChildren())
|
||||||
|
.mergeMap(
|
||||||
|
t => offlineMode ? of (<any>t) : fromPromise(this.compiler.compileModuleAsync(t)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {APP_BASE_HREF, HashLocationStrategy, Location, LocationStrategy, PathLocationStrategy, PlatformLocation} from '@angular/common';
|
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 {ExtraOptions, ROUTER_CONFIGURATION, provideRouterConfig, provideRouterInitializer, provideRoutes, rootRoute, setupRouter} from './common_router_providers';
|
||||||
import {Routes} from './config';
|
import {Routes} from './config';
|
||||||
|
@ -42,7 +42,7 @@ export const ROUTER_PROVIDERS: any[] = [
|
||||||
useFactory: setupRouter,
|
useFactory: setupRouter,
|
||||||
deps: [
|
deps: [
|
||||||
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
|
ApplicationRef, ComponentResolver, UrlSerializer, RouterOutletMap, Location, Injector,
|
||||||
NgModuleFactoryLoader, ROUTES, ROUTER_CONFIGURATION
|
NgModuleFactoryLoader, Compiler, ROUTES, ROUTER_CONFIGURATION
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
|
RouterOutletMap, {provide: ActivatedRoute, useFactory: rootRoute, deps: [Router]},
|
||||||
|
|
|
@ -122,7 +122,7 @@ export function andObservables(observables: Observable<Observable<any>>): Observ
|
||||||
return observables.mergeAll().every(result => result === true);
|
return observables.mergeAll().every(result => result === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapIntoObservable<T>(value: T | Observable<T>): Observable<T> {
|
export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> {
|
||||||
if (value instanceof Observable) {
|
if (value instanceof Observable) {
|
||||||
return value;
|
return value;
|
||||||
} else if (value instanceof Promise) {
|
} else if (value instanceof Promise) {
|
||||||
|
|
|
@ -1608,6 +1608,33 @@ describe('Integration', () => {
|
||||||
expect(fixture.debugElement.nativeElement).toHaveText('lazy-loaded');
|
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',
|
it('error emit an error when cannot load a config',
|
||||||
fakeAsync(inject(
|
fakeAsync(inject(
|
||||||
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
|
[Router, TestComponentBuilder, Location, NgModuleFactoryLoader],
|
||||||
|
|
|
@ -40,9 +40,11 @@ export class SpyNgModuleFactoryLoader implements NgModuleFactoryLoader {
|
||||||
|
|
||||||
function setupTestingRouter(
|
function setupTestingRouter(
|
||||||
resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap,
|
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(
|
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,
|
useFactory: setupTestingRouter,
|
||||||
deps: [
|
deps: [
|
||||||
ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader,
|
ComponentResolver, UrlSerializer, RouterOutletMap, Location, NgModuleFactoryLoader,
|
||||||
Injector, ROUTES
|
Compiler, Injector, ROUTES
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
@ -75,6 +75,12 @@ export interface ExtraOptions {
|
||||||
useHash?: boolean;
|
useHash?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare type LoadChildren = string | LoadChildrenCallback;
|
||||||
|
|
||||||
|
/** @experimental */
|
||||||
|
export declare type LoadChildrenCallback = () => Type<any> | Promise<Type<any>> | Observable<Type<any>>;
|
||||||
|
|
||||||
/** @stable */
|
/** @stable */
|
||||||
export declare class NavigationCancel {
|
export declare class NavigationCancel {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -156,7 +162,7 @@ export interface Route {
|
||||||
children?: Route[];
|
children?: Route[];
|
||||||
component?: Type<any> | string;
|
component?: Type<any> | string;
|
||||||
data?: Data;
|
data?: Data;
|
||||||
loadChildren?: string;
|
loadChildren?: LoadChildren;
|
||||||
outlet?: string;
|
outlet?: string;
|
||||||
path?: string;
|
path?: string;
|
||||||
pathMatch?: string;
|
pathMatch?: string;
|
||||||
|
@ -172,7 +178,7 @@ export declare class Router {
|
||||||
/** @experimental */ navigated: boolean;
|
/** @experimental */ navigated: boolean;
|
||||||
routerState: RouterState;
|
routerState: RouterState;
|
||||||
url: string;
|
url: string;
|
||||||
constructor(rootComponentType: Type<any>, resolver: ComponentResolver, urlSerializer: UrlSerializer, outletMap: RouterOutletMap, location: Location, injector: Injector, loader: NgModuleFactoryLoader, config: Routes);
|
constructor(rootComponentType: Type<any>, 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;
|
createUrlTree(commands: any[], {relativeTo, queryParams, fragment, preserveQueryParams, preserveFragment}?: NavigationExtras): UrlTree;
|
||||||
dispose(): void;
|
dispose(): void;
|
||||||
initialNavigation(): void;
|
initialNavigation(): void;
|
||||||
|
|
Loading…
Reference in New Issue